﻿' Copyright © Microsoft Corporation.  All Rights Reserved.
' This code released under the terms of the 
' Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
' <copyright file="MediaPlayer.vb" company="Microsoft">
'     Copyright © Microsoft Corporation. All rights reserved.
' </copyright>
' <summary>Implements the MediaPlayer class</summary>
' <author>Microsoft Expression Encoder Team</author>

Imports Microsoft.VisualBasic
Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Globalization
Imports System.Windows
Imports System.Windows.Browser
Imports System.Windows.Controls
Imports System.Windows.Controls.Primitives
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Threading
Imports System.IO.IsolatedStorage
Imports System.Net.NetworkInformation
Imports System.Windows.Automation.Peers
Imports System.Windows.Automation.Provider

Namespace ExpressionMediaPlayer
	''' <summary>
	''' This class represents the base class for a MediaPlayer control.
	''' </summary>
    <TemplatePart(Name:="stretchBox", Type:=GetType(FrameworkElement)), TemplatePart(Name:="videoWindow", Type:=GetType(FrameworkElement)), TemplatePart(Name:="mediaElement", Type:=GetType(MediaElement)), TemplatePart(Name:="startButton", Type:=GetType(ButtonBase)), TemplatePart(Name:="playPauseButton", Type:=GetType(ToggleButton)), TemplatePart(Name:="previousButton", Type:=GetType(ButtonBase)), TemplatePart(Name:="nextButton", Type:=GetType(ButtonBase)), TemplatePart(Name:="stopButton", Type:=GetType(ButtonBase)), TemplatePart(Name:="fullScreenButton", Type:=GetType(ButtonBase)), TemplatePart(Name:="offlineButton", Type:=GetType(ButtonBase)), TemplatePart(Name:="popOutButton", Type:=GetType(ButtonBase)), TemplatePart(Name:="plugInButton", Type:=GetType(ToggleButton)), TemplatePart(Name:="muteButton", Type:=GetType(ToggleButton)), TemplatePart(Name:="closedCaptionsButton", Type:=GetType(ToggleButton)), TemplatePart(Name:="volumeDownButton", Type:=GetType(ButtonBase)), TemplatePart(Name:="volumeUpButton", Type:=GetType(ButtonBase)), TemplatePart(Name:="playlistButton", Type:=GetType(ToggleButton)), TemplatePart(Name:="chapterButton", Type:=GetType(ToggleButton)), TemplatePart(Name:="positionSlider", Type:=GetType(SensitiveSlider)), TemplatePart(Name:="volumeSlider", Type:=GetType(SensitiveSlider)), TemplatePart(Name:="playlistSelector", Type:=GetType(Selector)), TemplatePart(Name:="chaptersSelector", Type:=GetType(Selector)), TemplatePart(Name:="errorMessage", Type:=GetType(FrameworkElement)), TemplatePart(Name:="closedCaptionBackground", Type:=GetType(FrameworkElement)), TemplatePart(Name:="gridPlugIn", Type:=GetType(Grid))> _
    Partial Public Class MediaPlayer
        Inherits Control
#Region "DP definitions"
        ''' <summary>
        ''' Using a DependencyProperty as the backing store for Playlist.  This enables animation, styling, binding, etc...        
        ''' </summary>
        Public Shared ReadOnly PlaylistProperty As DependencyProperty = DependencyProperty.Register("Playlist", GetType(Playlist), GetType(MediaPlayer), New PropertyMetadata(AddressOf PlaylistChanged))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for PlaybackPosition.  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly PlaybackPositionProperty As DependencyProperty = DependencyProperty.Register("PlaybackPosition", GetType(Double), GetType(MediaPlayer), New PropertyMetadata(Nothing))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for PlaybackPositionText.  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly PlaybackPositionTextProperty As DependencyProperty = DependencyProperty.Register("PlaybackPositionText", GetType(String), GetType(MediaPlayer), New PropertyMetadata(Nothing))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for PlaybackDuration.  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly PlaybackDurationProperty As DependencyProperty = DependencyProperty.Register("PlaybackDuration", GetType(Double), GetType(MediaPlayer), New PropertyMetadata(Nothing))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for PlaybackDurationText.  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly PlaybackDurationTextProperty As DependencyProperty = DependencyProperty.Register("PlaybackDurationText", GetType(String), GetType(MediaPlayer), New PropertyMetadata(String.Empty))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for BufferingPercent.  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly BufferingPercentProperty As DependencyProperty = DependencyProperty.Register("BufferingPercent", GetType(Double), GetType(MediaPlayer), New PropertyMetadata(0.0))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for PosterImageSource.  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly PosterImageSourceProperty As DependencyProperty = DependencyProperty.Register("PosterImageSource", GetType(String), GetType(MediaPlayer), New PropertyMetadata(Nothing))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for PosterImageMaxWidth.  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly PosterImageMaxWidthProperty As DependencyProperty = DependencyProperty.Register("PosterImageMaxWidth", GetType(Double), GetType(MediaPlayer), New PropertyMetadata(Double.PositiveInfinity))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for PosterImageMaxHeight.  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly PosterImageMaxHeightProperty As DependencyProperty = DependencyProperty.Register("PosterImageMaxHeight", GetType(Double), GetType(MediaPlayer), New PropertyMetadata(Double.PositiveInfinity))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for BufferingControlVisibility  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly BufferingControlVisibilityProperty As DependencyProperty = DependencyProperty.Register("BufferingControlVisibility", GetType(Visibility), GetType(MediaPlayer), New PropertyMetadata(Visibility.Collapsed))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for OfflineDownloadProgressVisibility  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly OfflineDownloadProgressVisibilityProperty As DependencyProperty = DependencyProperty.Register("OfflineDownloadProgressVisibility", GetType(Visibility), GetType(MediaPlayer), New PropertyMetadata(Visibility.Collapsed))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for DownloadOffsetPercent.  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly DownloadOffsetPercentProperty As DependencyProperty = DependencyProperty.Register("DownloadOffsetPercent", GetType(Double), GetType(MediaPlayer), New PropertyMetadata(0.0))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for DownloadPercent.  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly DownloadPercentProperty As DependencyProperty = DependencyProperty.Register("DownloadPercent", GetType(Double), GetType(MediaPlayer), New PropertyMetadata(0.0))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for PlaybackPositionText.  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly CaptionTextProperty As DependencyProperty = DependencyProperty.Register("CaptionText", GetType(String), GetType(MediaPlayer), New PropertyMetadata(String.Empty))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for CaptionsEnabled.
        ''' </summary>
        Public Shared ReadOnly CaptionsVisibilityProperty As DependencyProperty = DependencyProperty.Register("CaptionsVisibility", GetType(Visibility), GetType(MediaPlayer), New PropertyMetadata(Visibility.Visible))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for CaptionsButtonVisibilityProperty.  This may show/hide the closed caption button.
        ''' </summary>
        Public Shared ReadOnly CaptionsButtonVisibilityProperty As DependencyProperty = DependencyProperty.Register("CaptionsButtonVisibility", GetType(Visibility), GetType(MediaPlayer), New PropertyMetadata(Visibility.Visible))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for OfflineButtonVisibilityProperty.  This may show/hide the offline button.
        ''' </summary>
        Public Shared ReadOnly OfflineButtonVisibilityProperty As DependencyProperty = DependencyProperty.Register("OfflineButtonVisibility", GetType(Visibility), GetType(MediaPlayer), New PropertyMetadata(Visibility.Visible))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for OfflineButtonVisibilityProperty.  This will enable/disable the offline button.
        ''' </summary>
        Public Shared ReadOnly OfflineButtonEnabledProperty As DependencyProperty = DependencyProperty.Register("OfflineButtonEnabled", GetType(Boolean), GetType(MediaPlayer), New PropertyMetadata(True))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for PopOutButtonVisibilityProperty.  This may show/hide the popout button.
        ''' </summary>
        Public Shared ReadOnly PopOutButtonVisibilityProperty As DependencyProperty = DependencyProperty.Register("PopOutButtonVisibility", GetType(Visibility), GetType(MediaPlayer), New PropertyMetadata(Visibility.Visible))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for PlugInButtonVisibilityProperty.  This may show/hide the PlugIn button.
        ''' </summary>
        Public Shared ReadOnly PlugInButtonVisibilityProperty As DependencyProperty = DependencyProperty.Register("PlugInButtonVisibility", GetType(Visibility), GetType(MediaPlayer), New PropertyMetadata(Visibility.Visible))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for ChaptersButtonVisibilityProperty.  This may show/hide the popout button.
        ''' </summary>
        Public Shared ReadOnly ChaptersButtonVisibilityProperty As DependencyProperty = DependencyProperty.Register("ChaptersButtonVisibility", GetType(Visibility), GetType(MediaPlayer), New PropertyMetadata(Visibility.Visible))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for PlaylistButtonVisibilityProperty.  This may show/hide the popout button.
        ''' </summary>
        Public Shared ReadOnly PlaylistButtonVisibilityProperty As DependencyProperty = DependencyProperty.Register("PlaylistButtonVisibility", GetType(Visibility), GetType(MediaPlayer), New PropertyMetadata(Visibility.Visible))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for UserBackgroundColor.
        ''' </summary>
        Public Shared ReadOnly UserBackgroundColorProperty As DependencyProperty = DependencyProperty.Register("UserBackgroundColor", GetType(Brush), GetType(MediaPlayer), New PropertyMetadata(Nothing))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for HideOnClick.  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly HideOnClickProperty As DependencyProperty = DependencyProperty.RegisterAttached("HideOnClick", GetType(Boolean), GetType(MediaPlayer), New PropertyMetadata(False))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for FrameStepVisibility.  This enables animation, styling, binding, etc...
        ''' </summary>
        Public Shared ReadOnly FrameStepVisibilityProperty As DependencyProperty = DependencyProperty.Register("FrameStepVisibility", GetType(Visibility), GetType(MediaPlayer), New PropertyMetadata(Visibility.Collapsed))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for ErrorMessageVisibilityProperty.  This may show/hide the error message
        ''' </summary>
        Public Shared ReadOnly ErrorMessageVisibilityProperty As DependencyProperty = DependencyProperty.Register("ErrorMessageVisibility", GetType(Visibility), GetType(MediaPlayer), New PropertyMetadata(Visibility.Collapsed))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for ErrorMessage.  
        ''' </summary>
        Public Shared ReadOnly ErrorMessageProperty As DependencyProperty = DependencyProperty.Register("ErrorMessage", GetType(String), GetType(MediaPlayer), New PropertyMetadata(String.Empty))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for ButtonPreviousIsEnabled. 
        ''' </summary>
        Public Shared ReadOnly ButtonPreviousIsEnabledProperty As DependencyProperty = DependencyProperty.Register("ButtonPreviousIsEnabled", GetType(Boolean), GetType(MediaPlayer), New PropertyMetadata(False))

        ''' <summary>
        ''' Using a DependencyProperty as the backing store for ButtonNextIsEnabled. 
        ''' </summary>
        Public Shared ReadOnly ButtonNextIsEnabledProperty As DependencyProperty = DependencyProperty.Register("ButtonNextIsEnabled", GetType(Boolean), GetType(MediaPlayer), New PropertyMetadata(False))

#End Region

        ''' <summary>
        ''' String for the stretch box template element.
        ''' </summary>
        Protected Const StretchBox As String = "stretchBox"

        ''' <summary>
        ''' String for the video window template element.
        ''' </summary>
        Protected Const VideoWindow As String = "videoWindow"

        ''' <summary>
        ''' String for the media element template item.
        ''' </summary>
        Protected Const MediaElement As String = "mediaElement"

        ''' <summary>
        ''' String for the start button template element.
        ''' </summary>
        Protected Const StartButton As String = "startButton"

        ''' <summary>
        ''' String for the play pause button template element.
        ''' </summary>
        Protected Const PlayPauseButton As String = "playPauseButton"

        ''' <summary>
        ''' String for the previous button template element.
        ''' </summary>
        Protected Const PreviousButton As String = "previousButton"

        ''' <summary>
        ''' String for the next button template element.
        ''' </summary>
        Protected Const NextButton As String = "nextButton"

        ''' <summary>
        ''' String for the stop button template element.
        ''' </summary>
        Protected Const StopButton As String = "stopButton"

        ''' <summary>
        ''' String for the full screen template element.
        ''' </summary>
        Protected Const FullScreenButton As String = "fullScreenButton"

        ''' <summary>
        ''' String for the offline button element.
        ''' </summary>
        Protected Const OfflineButton As String = "offlineButton"

        ''' <summary>
        ''' String for the popout button element.
        ''' </summary>
        Protected Const PopOutButton As String = "popOutButton"

        ''' <summary>
        ''' String for the PlugIn button element.
        ''' </summary>
        Protected Const PlugInButton As String = "plugInButton"

        ''' <summary>
        ''' String for the mute button template element.
        ''' </summary>
        Protected Const MuteButton As String = "muteButton"

        ''' <summary>
        ''' String for the closed captions button template element.
        ''' </summary>
        Protected Const ClosedCaptionButton As String = "closedCaptionsButton"

        ''' <summary>
        ''' String for the volume down button template element.
        ''' </summary>
        Protected Const VolumeDownButton As String = "volumeDownButton"

        ''' <summary>
        ''' String for the volume up button template element.
        ''' </summary>
        Protected Const VolumeUpButton As String = "volumeUpButton"

        ''' <summary>
        ''' String for the playlist button template element.
        ''' </summary>
        Protected Const PlaylistButton As String = "playlistButton"

        ''' <summary>
        ''' String for the chapter button template element.
        ''' </summary>
        Protected Const ChapterButton As String = "chapterButton"

        ''' <summary>
        ''' String for the volume slider template element.
        ''' </summary>
        Protected Const VolumeSlider As String = "volumeSlider"

        ''' <summary>
        ''' String for the step forwards button template element.
        ''' </summary>
        Protected Const ButtonStepForwards As String = "stepForwardsButton"

        ''' <summary>
        ''' String for the step backwards template element.
        ''' </summary>
        Protected Const ButtonStepBackwards As String = "stepBackwardsButton"

        ''' <summary>
        ''' String for the position slider template element.
        ''' </summary>
        Protected Const PositionSlider As String = "positionSlider"

        ''' <summary>
        ''' String for the playlist selector template element.
        ''' </summary>
        Protected Const PlaylistSelector As String = "playlistSelector"

        ''' <summary>
        ''' String for the chapters selector template element.
        ''' </summary>
        Protected Const ChaptersSelector As String = "chaptersSelector"

        ''' <summary>
        ''' String for the error messages template element.
        ''' </summary>
        Protected Const ErrorMessageElement As String = "errorMessage"

        ''' <summary>
        ''' String for the closed captions background template element.
        ''' </summary>
        Protected Const ClosedCaptionBackground As String = "closedCaptionBackground"

        ''' <summary>
        ''' String for the plugin UI template element.
        ''' </summary>
        Protected Const GridPlugIn As String = "gridPlugIn"

        ''' <summary>
        ''' String for the enter FullScreen animation.
        ''' </summary>
        Protected Const EnterFullScreen As String = "enterFullScreen"

        ''' <summary>
        ''' String for the exit FullScreen animation.
        ''' </summary>
        Protected Const ExitFullScreen As String = "exitFullScreen"

        ''' <summary>
        ''' Minimum time to skip when seeking.
        ''' </summary>
        Private Const MinDelta As Double = 5.0

        ''' <summary>
        ''' Buffer for skip padding.
        ''' </summary>
        Private Const SkipBuffer As Double = 1.0

        ''' <summary>
        ''' The increment for skipping steps.
        ''' </summary>
        Private Const SkipSteps As Double = 10.0

        ''' <summary>
        ''' Default volume.
        ''' </summary>
        Private Const VolumeDefault As Double = 1.0

        ''' <summary>
        ''' Threshold for volume muting.
        ''' </summary>
        Private Const VolumeMuteThreshold As Double = 0.01

        ''' <summary>
        ''' Marker type element name.
        ''' </summary>
        Protected Const MarkerType As String = "NAME"

        ''' <summary>
        ''' Caption type element name.
        ''' </summary>
        Protected Const CaptionType As String = "CAPTION"

        ''' <summary>
        ''' Stretch box framework element.
        ''' </summary>
        Private m_elementStretchBox As FrameworkElement

        ''' <summary>
        ''' VideoWindow framework element -- used for internal sizing calculations
        ''' </summary>
        Private m_elementVideoWindow As FrameworkElement

        ''' <summary>
        ''' The main media element for playing audio and video.
        ''' </summary>
        Private m_mediaElement As MediaElement

        ''' <summary>
        ''' The start button.
        ''' </summary>
        Private m_buttonStart As ButtonBase

        ''' <summary>
        ''' Pause and play button.
        ''' </summary>
        Private m_buttonPlayPause As ToggleButton

        ''' <summary>
        ''' The previous button.
        ''' </summary>
        Private m_buttonPrevious As ButtonBase

        ''' <summary>
        ''' The next button.
        ''' </summary>
        Private m_buttonNext As ButtonBase

        ''' <summary>
        ''' The stop button.
        ''' </summary>
        Private m_buttonStop As ButtonBase

        ''' <summary>
        ''' Step forwards button.
        ''' </summary>
        Private m_buttonStepForwards As ButtonBase

        ''' <summary>
        ''' Step backwards button.
        ''' </summary>
        Private m_buttonStepBackwards As ButtonBase

        ''' <summary>
        ''' Button which toggles the playlist control.
        ''' </summary>
        Private m_buttonPlaylist As ToggleButton

        ''' <summary>
        ''' Button which toggles the chapter control.
        ''' </summary>
        Private m_buttonChapter As ToggleButton

        ''' <summary>
        ''' Full screen button.
        ''' </summary>
        Private m_buttonFullScreen As ButtonBase

        ''' <summary>
        ''' Offline button.
        ''' </summary>
        Private m_buttonOffline As ButtonBase

        ''' <summary>
        ''' PopOut button.
        ''' </summary>
        Private m_buttonPopOut As ButtonBase

        ''' <summary>
        ''' PlugIn button.
        ''' </summary>
        Private m_buttonPlugIn As ToggleButton

        ''' <summary>
        ''' Button which toggles muting the audio stream.
        ''' </summary>
        Private m_buttonMute As ToggleButton

        ''' <summary>
        ''' Button which toggles closed captions.
        ''' </summary>
        Private m_buttonClosedCaptions As ToggleButton

        ''' <summary>
        ''' Button which turns the volume down.
        ''' </summary>
        Private m_buttonVolumeDown As ButtonBase

        ''' <summary>
        ''' Button which turns the volume up.
        ''' </summary>
        Private m_buttonVolumeUp As ButtonBase

        ''' <summary>
        ''' Volume slider.
        ''' </summary>
        Private m_sliderVolume As SensitiveSlider

        ''' <summary>
        ''' Position slider.
        ''' </summary>
        Private m_sliderPosition As SensitiveSlider

        ''' <summary>
        ''' ListBox for the playlist control.
        ''' </summary>
        Private m_selectorPlaylist As Selector

        ''' <summary>
        ''' ListBox for the chapters control.
        ''' </summary>
        Private m_selectorChapters As Selector

        ''' <summary>
        ''' Dispatch timer for posting UI messages.
        ''' </summary>
        Private m_timer As DispatcherTimer

        ''' <summary>
        ''' Message block for error messages.
        ''' </summary>
        Private m_elementErrorMessage As FrameworkElement

        ''' <summary>
        ''' element for closed captions background.
        ''' </summary>
        Private m_elementCaptionBackground As FrameworkElement

        ''' <summary>
        ''' grid for PlugIn 
        ''' </summary>
        Private m_gridPlugIn As Grid

        ''' <summary>
        ''' The index of the current playlist item.
        ''' </summary>
        Private m_currentPlaylistIndex As Integer

        ''' <summary>
        ''' Retry count on media element failures.
        ''' </summary>
        Private m_mediaFailureRetryCount As Integer

        ''' <summary>
        ''' The maximum number of Retries to attempt.
        ''' </summary>
        Private Const maxMediaFailureRetries As Integer = 5

        ''' <summary>
        ''' Set when  the current playlist item uses adpative streaming.
        ''' </summary>
        Private m_currentItemIsAdaptive As Boolean

        ''' <summary>
        ''' The index of the current chapter item.
        ''' </summary>
        Private m_currentChapterIndex As Integer

        ''' <summary>
        ''' Current play state.
        ''' </summary>
        Private m_inPlayState As Boolean

        ''' <summary>
        ''' Play state when user started dragging the PositionSlider thumb.
        ''' </summary>
        Private m_inPlayStateBeforeSliderPositionDrag As Boolean

        ''' <summary>
        ''' Flag for updating the download progress control.
        ''' </summary>
        Private m_downloadProgressNeedsUpdating As Boolean

        ''' <summary>
        ''' The last time the media element was clicked.
        ''' </summary>
        Private m_lastMediaElementClick As New Date(0)

        ''' <summary>
        ''' Dispatch timer for fading out controls.
        ''' </summary>
        Private m_timerControlFadeOut As DispatcherTimer

        ''' <summary>
        ''' Current control state.
        ''' </summary>
        Private currentControlState As String

        ''' <summary>
        ''' Desired control state.
        ''' </summary>
        Private desiredControlState As String

        ''' <summary>
        ''' Flag which tracks whether we should perform a seek on the next tick.
        ''' </summary>
        Private m_seekOnNextTick As Boolean

        ''' <summary>
        ''' The position to seek to when m_seekOnNextTick is set.
        ''' </summary>
        Private m_seekOnNextTickPosition As Double

        ''' <summary>
        ''' Flag which tracks whether we should goto a different next playlist item on the next tick.
        ''' </summary>
        Private m_goToItemOnNextTick As Boolean

        ''' <summary>
        ''' The item to goto when m_goToItemOnNextTick is set.
        ''' </summary>
        Private m_goToItemOnNextTickIndex As Integer

        ''' <summary>
        ''' Flag for tracking mute status in volume slider control.
        ''' </summary>
        Private m_mutedCache As Boolean

        ''' <summary>
        ''' Current unmuted volume.
        ''' </summary>
        Private m_dblUnMutedVolume As Double = 0.5

        ''' <summary>
        ''' Initializes a new instance of the MediaPlayer class.
        ''' </summary>
        Public Sub New()
            DefaultStyleKey = GetType(MediaPlayer)
            DataContext = New ExpressionMediaPlayer.Resources()
            SetValue(PlaylistProperty, New Playlist())
            SetPlaybackPosition(0.0)
            SetPlaybackDuration(0.0)
            Dim timeCodeZero As New TimeCode(0.0, SmpteFrameRate.Unknown)
            Dim stringZero As String = timeCodeZero.ToString()
            SetPlaybackPositionText(stringZero)
            SetPlaybackDurationText(stringZero)

            SetBufferingPercent(0)
            SetDownloadOffsetPercent(0)
            SetDownloadPercent(0)
            UpdateCanStep()

            If (Not IsDesignTime) AndAlso HtmlPage.IsEnabled Then
                HtmlPage.RegisterScriptableObject("Player", Me)
            End If

            AddHandler Application.Current.InstallStateChanged, AddressOf PlayerInstallStateChanged
            AddHandler NetworkChange.NetworkAddressChanged, AddressOf PlayerNetworkAddressChanged
        End Sub

#Region "events"
        ''' <summary>
        ''' Event which fires when the state of this MediaPlayer changes.
        ''' </summary>
        <ScriptableMember()> _
        Public Event StateChanged As RoutedEventHandler

        ''' <summary>
        ''' Event which fires when a marker is reached.
        ''' </summary>
        <ScriptableMember()> _
        Public Event MarkerReached As ScriptableTimelineMarkerRoutedEventHandler
#End Region

#Region "properties"

        ''' <summary>
        ''' Gets the list of items to play.
        ''' </summary>
        <ScriptableMember(), Category("Media"), Description("Playlist settings")> _
        Public Property Playlist() As Playlist
            Get
                Return CType(GetValue(PlaylistProperty), Playlist)
            End Get
            Set(ByVal value As Playlist)
                SetValue(PlaylistProperty, value)
            End Set
        End Property

        ''' <summary>
        ''' Gets the index of the item currently selected.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never), ScriptableMember()> _
        Public ReadOnly Property CurrentPlaylistIndex() As Integer
            Get
                Return m_currentPlaylistIndex
            End Get
        End Property

        ''' <summary>
        ''' Gets the current PlaylistItem selected.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never), ScriptableMember()> _
        Public ReadOnly Property CurrentPlaylistItem() As PlaylistItem
            Get
                If m_currentPlaylistIndex >= 0 AndAlso m_currentPlaylistIndex < Playlist.Items.Count Then
                    Return Playlist.Items(m_currentPlaylistIndex)
                End If

                Return Nothing
            End Get
        End Property

        ''' <summary>
        ''' Gets or sets the current playback position.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never), ScriptableMember()> _
        Public ReadOnly Property PlaybackPosition() As Double
            Get
                Return CType(GetValue(PlaybackPositionProperty), Double)
            End Get
        End Property

        ''' <summary>
        ''' Gets the text of the playback position.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property PlaybackPositionText() As String
            Get
                Return CType(GetValue(PlaybackPositionTextProperty), String)
            End Get
        End Property

        ''' <summary>
        ''' Gets the duration of the current playlist item.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never), ScriptableMember()> _
        Public ReadOnly Property PlaybackDuration() As Double
            Get
                Return CType(GetValue(PlaybackDurationProperty), Double)
            End Get
        End Property

        ''' <summary>
        ''' Gets the current duration as a string.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property PlaybackDurationText() As String
            Get
                Return CType(GetValue(PlaybackDurationTextProperty), String)
            End Get
        End Property

        ''' <summary>
        ''' Gets the current buffering percent.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never), ScriptableMember()> _
        Public ReadOnly Property BufferingPercent() As Double
            Get
                Return CType(GetValue(BufferingPercentProperty), Double)
            End Get
        End Property

        ''' <summary>
        ''' Gets the source of the current poster image.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property PosterImageSource() As Uri
            Get
                Return CType(GetValue(PosterImageSourceProperty), Uri)
            End Get
        End Property

        ''' <summary>
        ''' Gets the max width of the current poster image.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property PosterImageMaxWidth() As Double
            Get
                Return CType(GetValue(PosterImageMaxWidthProperty), Double)
            End Get
        End Property

        ''' <summary>
        ''' Gets the max height of the current poster image.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property PosterImageMaxHeight() As Double
            Get
                Return CType(GetValue(PosterImageMaxHeightProperty), Double)
            End Get
        End Property

        ''' <summary>
        ''' Gets the visibility for the buffering control.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property BufferingControlVisibility() As Visibility
            Get
                Return CType(GetValue(BufferingControlVisibilityProperty), Visibility)
            End Get
        End Property

        ''' <summary>
        ''' Gets the visibility for the Offline download control.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property OfflineDownloadProgressVisibility() As Visibility
            Get
                Return CType(GetValue(OfflineDownloadProgressVisibilityProperty), Visibility)
            End Get
        End Property

        ''' <summary>
        ''' Gets the dowloading offset percent.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never), ScriptableMember()> _
        Public ReadOnly Property DownloadOffsetPercent() As Double
            Get
                Return CType(GetValue(DownloadOffsetPercentProperty), Double)
            End Get
        End Property

        ''' <summary>
        ''' Gets the downloaded percent.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never), ScriptableMember()> _
        Public ReadOnly Property DownloadPercent() As Double
            Get
                Return CType(GetValue(DownloadPercentProperty), Double)
            End Get
        End Property

        ''' <summary>
        ''' Gets the visibility of the captions button.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property CaptionsButtonVisibility() As Visibility
            Get
                Return CType(GetValue(CaptionsButtonVisibilityProperty), Visibility)
            End Get
        End Property

        ''' <summary>
        ''' Gets the visibility of the offline button.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property OfflineButtonVisibility() As Visibility
            Get
                Return CType(GetValue(OfflineButtonVisibilityProperty), Visibility)
            End Get
        End Property
        ''' <summary>
        ''' Gets whether the offline button is enabled
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property OfflineButtonEnabled() As Boolean
            Get
                Return CType(GetValue(OfflineButtonEnabledProperty), Boolean)
            End Get
        End Property

        ''' <summary>
        ''' Gets the visibility of the popout button.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property PopOutButtonVisibility() As Visibility
            Get
                Return CType(GetValue(PopOutButtonVisibilityProperty), Visibility)
            End Get
        End Property

        ''' <summary>
        ''' Gets the visibility of the PlugIn button.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property PlugInButtonVisibility() As Visibility
            Get
                Return CType(GetValue(PlugInButtonVisibilityProperty), Visibility)
            End Get
        End Property

        ''' <summary>
        ''' Gets the visibility of the chapters button.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property ChaptersButtonVisibility() As Visibility
            Get
                Return CType(GetValue(ChaptersButtonVisibilityProperty), Visibility)
            End Get
        End Property

        ''' <summary>
        ''' Gets the visibility of the Playlist button.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property PlaylistButtonVisibility() As Visibility
            Get
                Return CType(GetValue(PlaylistButtonVisibilityProperty), Visibility)
            End Get
        End Property

        ''' <summary>
        ''' Gets the caption text.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property CaptionText() As String
            Get
                Return CType(GetValue(CaptionTextProperty), String)
            End Get
        End Property

        ''' <summary>
        ''' Gets or sets the visibility of captions.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public Property CaptionsVisibility() As Visibility
            Get
                Return CType(GetValue(CaptionsVisibilityProperty), Visibility)
            End Get
            Set(ByVal value As Visibility)
                SetValue(CaptionsVisibilityProperty, value)
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the user background color.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public Property UserBackgroundColor() As Brush
            Get
                Return CType(GetValue(UserBackgroundColorProperty), Brush)
            End Get
            Set(ByVal value As Brush)
                SetValue(UserBackgroundColorProperty, value)
            End Set
        End Property

        ''' <summary>
        ''' Gets the current media element state.
        ''' </summary>
        <ScriptableMember(), System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property CurrentState() As MediaElementState
            Get
                If m_mediaElement IsNot Nothing Then
                    Return m_mediaElement.CurrentState
                End If

                Return MediaElementState.Stopped
            End Get
        End Property

        ''' <summary>
        ''' Gets a value indicating whether the media element can step.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property FrameStepVisibility() As Visibility
            Get
                Return CType(GetValue(FrameStepVisibilityProperty), Visibility)
            End Get
        End Property

        ''' <summary>
        ''' Gets a value indicating whether the error message is visible.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property ErrorMessageVisibility() As Visibility
            Get
                Return CType(GetValue(ErrorMessageVisibilityProperty), Visibility)
            End Get
        End Property

        ''' <summary>
        ''' Gets the source of the current poster image.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public ReadOnly Property ErrorMessage() As String
            Get
                Return CType(GetValue(ErrorMessageProperty), String)
            End Get
        End Property

        ''' <summary>
        ''' Gets a value indicating whether the PreviousButton control is enabled.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never), ScriptableMember()> _
        Public ReadOnly Property ButtonPreviousIsEnabled() As Boolean
            Get
                Return CBool(GetValue(ButtonPreviousIsEnabledProperty))
            End Get
        End Property

        ''' <summary>
        ''' Gets a value indicating whether the NextButton control is enabled.
        ''' </summary>
        <System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never), ScriptableMember()> _
        Public ReadOnly Property ButtonNextIsEnabled() As Boolean
            Get
                Return CBool(GetValue(ButtonNextIsEnabledProperty))
            End Get
        End Property

        ''' <summary>
        ''' Gets the current media element we are using.
        ''' </summary>
        Public ReadOnly Property CurrentMediaElement() As MediaElement
            Get
                Return m_mediaElement
            End Get
        End Property

        ''' <summary>
        ''' Sets the playback position of the position slider.
        ''' </summary>
        ''' <param name="value">The new playback position.</param>        
        Protected Overridable Sub SetPlaybackPosition(ByVal value As Double)
            If PlaybackPosition = value Then
                Return
            End If

            SetValue(PlaybackPositionProperty, value)
            If ((m_sliderPosition IsNot Nothing) AndAlso ((Not m_sliderPosition.IsDragging))) AndAlso ((Not m_seekOnNextTick)) Then ' don't update the slider position while there is a pending seek -- as this can generate an additional seek request -  don't update slider position while the user is dragging
                ' update slider position
                If m_sliderPosition.Value <> value Then
                    RemoveHandler m_sliderPosition.ValueChanged, AddressOf OnSliderPositionChanged ' Don't generate a seek
                    m_sliderPosition.Value = value
                    AddHandler m_sliderPosition.ValueChanged, AddressOf OnSliderPositionChanged
                End If
            End If

            ' update position text as well
            UpdatePositionDisplay()
        End Sub

        ''' <summary>
        ''' Sets the playback duration of the position slider.
        ''' </summary>
        ''' <param name="value">The new playback duration.</param>
        Protected Overridable Sub SetPlaybackDuration(ByVal value As Double)
            SetValue(PlaybackDurationProperty, value)
            If m_sliderPosition IsNot Nothing Then
                RemoveHandler m_sliderPosition.ValueChanged, AddressOf OnSliderPositionChanged ' Don't generate a seek
                m_sliderPosition.Value = 0.0
                m_sliderPosition.Maximum = PlaybackDuration
                AddHandler m_sliderPosition.ValueChanged, AddressOf OnSliderPositionChanged
            End If
        End Sub

        ''' <summary>
        ''' Sets the playback position text.
        ''' </summary>
        ''' <param name="value">New text for the playback position.</param>
        Protected Overridable Sub SetPlaybackPositionText(ByVal value As String)
            SetValue(PlaybackPositionTextProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the playback duration text.
        ''' </summary>
        ''' <param name="value">The new duration text.</param>
        Protected Overridable Sub SetPlaybackDurationText(ByVal value As String)
            SetValue(PlaybackDurationTextProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the buffering percentage.
        ''' </summary>
        ''' <param name="value">New value for the buffering percent.</param>
        Protected Sub SetBufferingPercent(ByVal value As Double)
            SetValue(BufferingPercentProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the poster image.
        ''' </summary>
        ''' <param name="value">New poster image.</param>
        Protected Overridable Sub SetPosterImageSource(ByVal value As String)
            SetValue(PosterImageSourceProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the poster image width.
        ''' </summary>
        ''' <param name="value">New poster image width.</param>
        Protected Overridable Sub SetPosterImageMaxWidth(ByVal value As Double)
            SetValue(PosterImageMaxWidthProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the poster image height.
        ''' </summary>
        ''' <param name="value">New poster image height.</param>
        Protected Overridable Sub SetPosterImageMaxHeight(ByVal value As Double)
            SetValue(PosterImageMaxHeightProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the visibility of the buffering control.
        ''' </summary>
        ''' <param name="value">New visibility for the buffering control.</param>
        Protected Overridable Sub SetBufferingControlVisibility(ByVal value As Visibility)
            SetValue(BufferingControlVisibilityProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the visibility of the Offline download progress bar.
        ''' </summary>
        ''' <param name="value">New visibility for the Offline download progress bar.</param>
        Friend Overridable Sub SetOfflineDownloadProgressVisibility(ByVal value As Visibility)
            SetValue(OfflineDownloadProgressVisibilityProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the download offset percent.
        ''' </summary>
        ''' <param name="value">New value for the download offset.</param>
        Protected Sub SetDownloadOffsetPercent(ByVal value As Double)
            SetValue(DownloadOffsetPercentProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the download percent.
        ''' </summary>
        ''' <param name="value">New value for the download percent.</param>
        Protected Sub SetDownloadPercent(ByVal value As Double)
            SetValue(DownloadPercentProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the caption text.
        ''' </summary>
        ''' <param name="value">New value for the caption text.</param>
        Protected Overridable Sub SetCaptionText(ByVal value As String)
            SetValue(CaptionTextProperty, value)

            If m_elementCaptionBackground IsNot Nothing Then
                If String.IsNullOrEmpty(value) Then
                    m_elementCaptionBackground.Height = 0.0
                Else
                    m_elementCaptionBackground.Height = Double.NaN ' Double.NaN is treated as "Auto" height
                End If
            End If
        End Sub

        ''' <summary>
        ''' Sets the visibility of the captions button.
        ''' </summary>
        ''' <param name="value">New visibility of the captions button.</param>
        Protected Overridable Sub SetCaptionsButtonVisibility(ByVal value As Visibility)
            SetValue(CaptionsButtonVisibilityProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the visibility of the offline button.
        ''' </summary>
        ''' <param name="value">New visibility of the offline button.</param>
        Friend Overridable Sub SetOfflineButtonVisibility(ByVal value As Visibility)
            SetValue(OfflineButtonVisibilityProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the visibility of the offline button.
        ''' </summary>
        ''' <param name="value">New visibility of the offline button.</param>
        Friend Overridable Sub SetOfflineButtonEnabled(ByVal value As Boolean)
            SetValue(OfflineButtonEnabledProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the visibility of the popout button.
        ''' </summary>
        ''' <param name="value">New visibility of the popout button.</param>
        Protected Overridable Sub SetPopOutButtonVisibility(ByVal value As Visibility)
            SetValue(PopOutButtonVisibilityProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the visibility of the PlugIn button.
        ''' </summary>
        ''' <param name="value">New visibility of the PlugIn button.</param>
        Protected Overridable Sub SetPlugInButtonVisibility(ByVal value As Visibility)
            If value = Visibility.Collapsed Then
                ShowPlugIn(False)
            Else
                If Me.m_buttonPlugIn IsNot Nothing Then
                    ShowPlugIn(CBool(Me.m_buttonPlugIn.IsChecked = True))
                End If
            End If
            SetValue(PlugInButtonVisibilityProperty, value)
        End Sub

        ''' <summary>
        ''' Sets the visibility of the chapters button.
        ''' </summary>
        ''' <param name="value">New visibility of the chapters button.</param>
        Protected Overridable Sub SetChaptersButtonVisibility(ByVal value As Visibility)
            SetValue(ChaptersButtonVisibilityProperty, value)
            If value = Visibility.Collapsed Then
                ShowChapters(False)
            End If
        End Sub

        ''' <summary>
        ''' Sets the visibility of the playlist button.
        ''' </summary>
        ''' <param name="value">New visibility of the playlist button.</param>
        Protected Overridable Sub SetPlaylistButtonVisibility(ByVal value As Visibility)
            SetValue(PlaylistButtonVisibilityProperty, value)
            If value = Visibility.Collapsed Then
                ShowPlaylist(False)
            End If
        End Sub

#End Region

#Region "Attached Properties"
        ''' <summary>
        ''' Gets the current value of the hide on click property.
        ''' </summary>
        ''' <param name="obj">Dependency property object.</param>
        ''' <returns>Flag indicating the status of the hide on click properry.</returns>
        Public Shared Function GetHideOnClick(ByVal obj As DependencyObject) As Boolean
            Return CBool(obj.GetValue(HideOnClickProperty))
        End Function

        ''' <summary>
        ''' Sets the value of the hide on click property.
        ''' </summary>
        ''' <param name="obj">Dependency property object.</param>
        ''' <param name="value">The new value of the hide on click property.</param>
        Public Shared Sub SetHideOnClick(ByVal obj As DependencyObject, ByVal value As Boolean)
            obj.SetValue(HideOnClickProperty, value)
        End Sub
#End Region

#Region "Utilties"

        ''' <summary>
        ''' Toggles full screen mode.
        ''' </summary>
        <ScriptableMember()> _
        Public Overridable Sub ToggleFullScreen()
            Application.Current.Host.Content.IsFullScreen = Not (Application.Current.Host.Content.IsFullScreen)
        End Sub

        ''' <summary>
        ''' causes window to popout
        ''' </summary>
        <ScriptableMember()> _
        Public Overridable Sub PopOutWindow()
            Try
                If HtmlPage.IsEnabled AndAlso HtmlPage.Document IsNot Nothing AndAlso HtmlPage.Document.DocumentUri IsNot Nothing AndAlso HtmlPage.IsPopupWindowAllowed Then
                    Pause()
                    If m_buttonPopOut IsNot Nothing Then
                        m_buttonPopOut.IsEnabled = False
                    End If

                    Dim popupOptions As New HtmlPopupWindowOptions()
                    popupOptions.Directories = False
                    popupOptions.Height = 480
                    popupOptions.Width = 640
                    If Playlist IsNot Nothing AndAlso Playlist.Items.Count > 0 AndAlso (Not Double.IsNaN(Playlist.Items(0).VideoWidth)) AndAlso (Not Double.IsNaN(Playlist.Items(0).VideoHeight)) Then
                        popupOptions.Width = CInt(Fix(Playlist.Items(0).VideoWidth + 0.5))
                        popupOptions.Height = CInt(Fix(Playlist.Items(0).VideoHeight + 0.5))
                    End If
                    popupOptions.Scrollbars = False
                    popupOptions.Status = False
                    popupOptions.Toolbar = False
                    popupOptions.Resizeable = True
                    popupOptions.Menubar = False
                    popupOptions.Location = False
                    HtmlPage.PopupWindow(HtmlPage.Document.DocumentUri, Nothing, popupOptions)
                End If
            Catch e1 As ArgumentException
                ' popup fails for local paths/UNC paths, in this case resume video playback
                Play()
            End Try
        End Sub

        ''' <summary>
        ''' Returns a System.Windows.Media.Color from a color format string.
        ''' </summary>
        ''' <param name="colorStr">String describing the color.</param>
        ''' <returns>The new System.Windows.Media.Color.</returns>
        Public Shared Function ColorFromString(ByVal colorStr As String) As Color
            Dim uiValue As UInt32 = UInt32.Parse(colorStr.Substring(1), System.Globalization.NumberStyles.AllowHexSpecifier Or System.Globalization.NumberStyles.HexNumber, CultureInfo.InvariantCulture)
            Dim a As Byte = CByte((uiValue >> &H18) And &HFF)
            Dim r As Byte = CByte((uiValue >> &H10) And &HFF)
            Dim g As Byte = CByte((uiValue >> &H8) And &HFF)
            Dim b As Byte = CByte((uiValue) And &HFF)
            Return Color.FromArgb(a, r, g, b)
        End Function

        ''' <summary>
        ''' Unescapes an escaped string.
        ''' </summary>
        ''' <param name="escaped">The escaped string.</param>
        ''' <returns>The new unescaped string.</returns>
        Public Shared Function UnEscape(ByVal escaped As String) As String
            Dim tmp As String = System.Uri.UnescapeDataString(escaped)
            tmp = tmp.Replace("%21", "!")
            tmp = tmp.Replace("%26", "&")
            tmp = tmp.Replace("%27", "'")
            tmp = tmp.Replace("%2C", ",")
            tmp = tmp.Replace("\""", """")
            tmp = tmp.Replace("\\", "\")
            Return tmp
        End Function

#End Region

#Region "public methods"

        ''' <summary>
        ''' Plays the current item in the playlist.
        ''' </summary>
        <ScriptableMember()> _
        Public Overridable Sub Play()
            If Not m_inPlayState Then
                TogglePlayPause()
            End If
        End Sub

        ''' <summary>
        ''' Pauses the current playlist item.
        ''' </summary>
        <ScriptableMember()> _
        Public Overridable Sub Pause()
            If m_inPlayState Then
                TogglePlayPause()
            End If
        End Sub

        ''' <summary>
        ''' Stops the current playlist item.
        ''' </summary>
        <ScriptableMember()> _
        Public Overridable Sub [Stop]()
            OnButtonClickStop(Nothing, Nothing)
        End Sub

        ''' <summary>
        ''' Current media position
        ''' </summary>
        <ScriptableMember()> _
        Public Overridable Property Position() As Double
            Get
                Return (m_mediaElement.Position.TotalSeconds)
            End Get
            Set(ByVal value As Double)
                m_mediaElement.Position = New TimeSpan(CLng(Fix(value * 10000000)))
                SeekToTime(value)
            End Set
        End Property

        ''' <summary>
        ''' Goes to the next playlist item at the next ui update interval
        ''' </summary>        
        Public Sub GoToPlaylistItemOnNextTick(ByVal playlistItemIndex As Integer)
            If Not m_goToItemOnNextTick Then ' don't set it if already set
                m_goToItemOnNextTick = True
                m_goToItemOnNextTickIndex = playlistItemIndex
            End If
        End Sub

        ''' <summary>
        ''' Goes to the next playlist item.
        ''' </summary>
        <ScriptableMember()> _
        Public Sub GoToNextPlaylistItem()
            GoToPlaylistItem(m_currentPlaylistIndex + 1)
        End Sub

        ''' <summary>
        ''' Goes to the next playlist item.
        ''' </summary>
        <ScriptableMember()> _
        Public Sub GoToPreviousPlaylistItem()
            GoToPlaylistItem(m_currentPlaylistIndex - 1)
        End Sub

        ''' <summary>
        ''' Goes to a playlist item.
        ''' </summary>
        ''' <param name="playlistItemIndex">The index of the playlist item to go to.</param>
        <ScriptableMember()> _
        Public Overridable Sub GoToPlaylistItem(ByVal playlistItemIndex As Integer)
            If IsDesignTime Then
                Return
            End If
#If DEBUG Then
            Debug.WriteLine("GoToPlaylistItem item=" & playlistItemIndex.ToString() & Date.Now.ToString())
#End If

            If playlistItemIndex >= 0 AndAlso playlistItemIndex < Playlist.Items.Count Then
                m_currentChapterIndex = 0
                ClearCaptionText()

                Dim canSkipReset As Boolean = m_currentPlaylistIndex = playlistItemIndex
                If canSkipReset Then
                    Select Case m_mediaElement.CurrentState
                        Case MediaElementState.Closed, MediaElementState.Stopped
                            canSkipReset = False
                        Case Else
                    End Select
                End If

                If Not canSkipReset Then
#If DEBUG Then
                    Debug.WriteLine("GoToPlaylistItem not skipping")
#End If
                    m_currentPlaylistIndex = playlistItemIndex
                    Dim playlistItem As PlaylistItem = Playlist.Items(m_currentPlaylistIndex)

                    If m_selectorChapters IsNot Nothing Then
                        m_selectorChapters.ItemsSource = playlistItem.Chapters
                        SetChaptersButtonVisibility(If(playlistItem.Chapters.Count > 0, Visibility.Visible, Visibility.Collapsed))
                    End If

                    ' Update window title with playlist item title (or filename)
                    If (Not Application.Current.IsRunningOutOfBrowser) AndAlso HtmlPage.IsEnabled AndAlso HtmlPage.Document IsNot Nothing Then
                        Dim newTitle As String = String.Empty
                        If Not String.IsNullOrEmpty(playlistItem.Title) Then
                            newTitle = playlistItem.Title
                        Else
                            If playlistItem.IsAdaptiveStreaming Then
                                newTitle = System.IO.Path.GetDirectoryName(playlistItem.MediaSource.ToString())
                                newTitle = System.IO.Path.GetFileName(newTitle)
                            Else
                                newTitle = System.IO.Path.GetFileName(playlistItem.MediaSource.ToString())
                            End If
                        End If

                        HtmlPage.Document.SetProperty("title", newTitle)
                    End If

                    ' Reset the media stream source to null
                    m_mediaStreamSource = Nothing
                    m_mediaFailureRetryCount = 0

                    DoOpenPlaylistItem(playlistItem)

                    ' Update and show the poster frame for the current item -- if not actively playing
                    If (Not m_inPlayState) AndAlso (Not Playlist.AutoPlay) Then
                        DisplayPoster(m_currentPlaylistIndex)
                    End If
                End If

                Position = 0.0
            ElseIf playlistItemIndex >= Playlist.Items.Count Then
                ' Reached end -- flag that playback is paused.
                m_inPlayState = False
            End If

            OnItemStarted()
        End Sub

        Private Sub DoOpenPlaylistItem(ByVal playlistItem As PlaylistItem)
            ' Attach media source to the MediaElement
            m_currentItemIsAdaptive = playlistItem.IsAdaptiveStreaming
            SetPlugInButtonVisibility(If(m_currentItemIsAdaptive, Visibility.Visible, Visibility.Collapsed))

            Try
                If m_currentItemIsAdaptive Then
                    Me.InitSmoothStreamingPlayback(playlistItem)
                Else
                    Me.UnloadSmoothStreaming()
                    If Me.IsOffline Then
                        Dim offlineIsoUri As IsoUri = Me.MakeOfflineIsoUri(playlistItem.MediaSource)
                        If offlineIsoUri.IsoFileExists Then
                            m_mediaElement.SetSource(offlineIsoUri.Stream)
                        Else
                            Dim strErrorMessage As String = String.Format(CultureInfo.CurrentUICulture, ExpressionMediaPlayer.Resources.errorInvalidMedia, playlistItem.MediaSource.ToString())
                            ShowErrorMessage(strErrorMessage)
                        End If
                    Else
                        Dim isoUri As New IsoUri(playlistItem.MediaSource)
                        m_mediaElement.Source = isoUri.FullyQualifiedUri
                    End If
                End If
            Catch iso As IsolatedStorageException
                ShowErrorMessage(iso.Message)
                Me.Stop()
            End Try
        End Sub
#End Region

#Region "TemplateHandlers"

        ''' <summary>
        ''' Overrides base.OnApplyTemplate().
        ''' </summary>
        Public Overrides Sub OnApplyTemplate()
            MyBase.OnApplyTemplate()

            UnhookHandlers()

            GetTemplateChildren()

            ConfigureBinding()
            ApplyProperties()
            HookHandlers()
        End Sub

        ''' <summary>
        ''' Gets the child elements of the template.
        ''' </summary>
        Protected Overridable Sub GetTemplateChildren()
            m_elementStretchBox = TryCast(GetTemplateChild(StretchBox), FrameworkElement)
            m_elementVideoWindow = TryCast(GetTemplateChild(VideoWindow), FrameworkElement)
            m_mediaElement = TryCast(GetTemplateChild(MediaElement), MediaElement)
            m_buttonStart = TryCast(GetTemplateChild(StartButton), ButtonBase)
            m_buttonPlayPause = TryCast(GetTemplateChild(PlayPauseButton), ToggleButton)
            m_buttonPrevious = TryCast(GetTemplateChild(PreviousButton), ButtonBase)
            m_buttonNext = TryCast(GetTemplateChild(NextButton), ButtonBase)
            m_buttonStop = TryCast(GetTemplateChild(StopButton), ButtonBase)
            m_buttonFullScreen = TryCast(GetTemplateChild(FullScreenButton), ButtonBase)
            m_buttonOffline = TryCast(GetTemplateChild(OfflineButton), ButtonBase)
            m_buttonPopOut = TryCast(GetTemplateChild(PopOutButton), ButtonBase)
            m_buttonPlugIn = TryCast(GetTemplateChild(PlugInButton), ToggleButton)
            m_sliderPosition = TryCast(GetTemplateChild(PositionSlider), SensitiveSlider)
            m_buttonMute = TryCast(GetTemplateChild(MuteButton), ToggleButton)
            m_buttonClosedCaptions = TryCast(GetTemplateChild(ClosedCaptionButton), ToggleButton)
            m_buttonVolumeDown = TryCast(GetTemplateChild(VolumeDownButton), ButtonBase)
            m_buttonVolumeUp = TryCast(GetTemplateChild(VolumeUpButton), ButtonBase)
            m_sliderVolume = TryCast(GetTemplateChild(VolumeSlider), SensitiveSlider)
            m_buttonPlaylist = TryCast(GetTemplateChild(PlaylistButton), ToggleButton)
            m_buttonChapter = TryCast(GetTemplateChild(ChapterButton), ToggleButton)
            m_selectorPlaylist = TryCast(GetTemplateChild(PlaylistSelector), Selector)
            m_selectorChapters = TryCast(GetTemplateChild(ChaptersSelector), Selector)
            m_elementErrorMessage = TryCast(GetTemplateChild(ErrorMessageElement), FrameworkElement)
            m_elementCaptionBackground = TryCast(GetTemplateChild(ClosedCaptionBackground), FrameworkElement)
            m_buttonStepForwards = TryCast(GetTemplateChild(ButtonStepForwards), ButtonBase)
            m_buttonStepBackwards = TryCast(GetTemplateChild(ButtonStepBackwards), ButtonBase)
            m_gridPlugIn = TryCast(GetTemplateChild(GridPlugIn), Grid)
        End Sub

#End Region

#Region "EventHandlers"

        ''' <summary>
        ''' Parse init params. It should be just a playlist so parse that.
        ''' </summary>
        ''' <param name="e">Event args.</param>
        Protected Overridable Sub ParseStartupParameters(ByVal e As StartupEventArgs)
            Dim strInitValue As String = String.Empty

            If e.InitParams.TryGetValue("playerSettings", strInitValue) Then
                Try
                    Dim strPlaylist As String = System.Uri.UnescapeDataString(strInitValue)
                    SetValue(PlaylistProperty, Playlist.Create(strPlaylist))
                    ApplyProperties()
                Catch xe As System.Xml.XmlException
                    ' special case. Encoder precompiled template? fail silently...
                    If strInitValue.IndexOf("<$=", StringComparison.OrdinalIgnoreCase) > 0 AndAlso strInitValue.IndexOf("$>", StringComparison.OrdinalIgnoreCase) > 0 Then
                        SetValue(PlaylistProperty, New Playlist())
                        Return
                    End If
                    ShowErrorMessage(xe.ToString())
                Catch xe As NullReferenceException
                    ShowErrorMessage(xe.ToString())
                Catch xe As InvalidCastException
                    ShowErrorMessage(xe.ToString())
                Catch xe As InvalidPlaylistException
                    ShowErrorMessage(xe.ToString())
                End Try
            End If
        End Sub
        ''' <summary>
        ''' playlist has changed, update visibility of buttons/lit etc.
        ''' </summary>
        Private Sub Playlist_PlaylistChanged(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ApplyProperties()
        End Sub
        ''' <summary>
        ''' playlist has changed, hook the playlist changed event
        ''' </summary>
        Private Sub PlaylistChanged()
            AddHandler Playlist.PlaylistChanged, AddressOf Playlist_PlaylistChanged
        End Sub
        ''' <summary>
        ''' Dependancy Property "Playlist" has changed. Need to hook the playlist changed
        ''' </summary>
        Private Shared Sub PlaylistChanged(ByVal dependancyProperty As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
            Dim player As MediaPlayer = TryCast(dependancyProperty, MediaPlayer)
            If player IsNot Nothing Then
                player.PlaylistChanged()
            End If
        End Sub
        ''' <summary>
        ''' Handler for the OnStartup event.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Public Overridable Sub OnStartup(ByVal sender As Object, ByVal e As StartupEventArgs)
            If Application.Current.InstallState = InstallState.NotInstalled Then
                ParseStartupParameters(e)
            Else
                Try
                    SetValue(PlaylistProperty, Playlist.ReadFromIsoStorage())
                    ApplyProperties()
                Catch xe As IsolatedStorageException
                    IsoUri.ClearIsoStorage()
                    ShowErrorMessage(xe.ToString())
                Catch xe As NullReferenceException
                    ShowErrorMessage(xe.ToString())
                Catch xe As InvalidCastException
                    ShowErrorMessage(xe.ToString())
                Catch xe As InvalidPlaylistException
                    ShowErrorMessage(xe.ToString())
                End Try
            End If
        End Sub

        ''' <summary>
        ''' Detect if we are running in Blend
        ''' </summary>
        ''' <returns>are we running in Blend?</returns>
        Protected Shared ReadOnly Property IsDesignTime() As Boolean
            Get
                Return Application.Current.GetType() Is GetType(Application)
            End Get
        End Property

        ''' <summary>
        ''' Event handler for the playlist clicked event.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickPlaylist(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim btn As ToggleButton = TryCast(sender, ToggleButton)
            ShowPlaylist(CBool(btn.IsChecked = True))
        End Sub

        ''' <summary>
        ''' show/hide playlist
        ''' </summary>
        ''' <param name="show">true for show, false for hide</param>
        <ScriptableMember()> _
        Public Overridable Sub ShowPlaylist(ByVal show As Boolean)
            VisualStateManager.GoToState(Me, If(show, "showPlaylist", "hidePlaylist"), True)
            If show AndAlso m_selectorPlaylist IsNot Nothing AndAlso m_selectorPlaylist.SelectedItem IsNot Nothing AndAlso TypeOf m_selectorPlaylist.SelectedItem Is ListBoxItem Then
                CType(m_selectorPlaylist.SelectedItem, ListBoxItem).Focus()
            End If
        End Sub

        ''' <summary>
        ''' Event handler for the chapter button event.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickChapter(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim btn As ToggleButton = TryCast(sender, ToggleButton)
            ShowChapters(CBool(btn.IsChecked = True))
        End Sub

        ''' <summary>
        ''' show/hide chapters control
        ''' </summary>
        ''' <param name="show">true for show, false for hide</param>
        <ScriptableMember()> _
        Public Overridable Sub ShowChapters(ByVal show As Boolean)
            VisualStateManager.GoToState(Me, If(show, "showChapters", "hideChapters"), True)
            If show AndAlso m_selectorChapters IsNot Nothing AndAlso m_selectorChapters.SelectedItem IsNot Nothing AndAlso TypeOf m_selectorChapters.SelectedItem Is ListBoxItem Then
                CType(m_selectorChapters.SelectedItem, ListBoxItem).Focus()
            End If
        End Sub

        ''' <summary>
        ''' get/set if chapters showing
        ''' </summary>
        <ScriptableMember()> _
        Public Overridable Property ChaptersVisible() As Boolean
            Get
                Return CBool(m_buttonChapter.IsChecked = True)
            End Get
            Set(ByVal value As Boolean)
                m_buttonChapter.IsChecked = value
                ShowChapters(value)
            End Set
        End Property

        ''' <summary>
        ''' set/get if playlist showing
        ''' </summary>
        <ScriptableMember()> _
        Public Overridable Property PlaylistVisible() As Boolean
            Get
                Return CBool(m_buttonPlaylist.IsChecked = True)
            End Get
            Set(ByVal value As Boolean)
                m_buttonPlaylist.IsChecked = value
                ShowPlaylist(value)
            End Set
        End Property

        ''' <summary>
        ''' Update the state of the buffering progress indicator.
        ''' </summary>
        Private Sub UpdateBufferingControls()
            ' control the visiblity of the buffering control
            If (m_currentPlaylistIndex >= 0) AndAlso (m_currentPlaylistIndex < Playlist.Items.Count) AndAlso ((Not Playlist.Items(m_currentPlaylistIndex).IsAdaptiveStreaming)) AndAlso (MediaElementState.Buffering = m_mediaElement.CurrentState) Then
                If BufferingControlVisibility <> Visibility.Visible Then
                    SetBufferingControlVisibility(Visibility.Visible)
                    VisualStateManager.GoToState(Me, "showBuffering", True)
                End If

                SetBufferingPercent(m_mediaElement.BufferingProgress * ProgressConst.Progress2Percent)
            Else
                If BufferingControlVisibility <> Visibility.Collapsed Then
                    SetBufferingControlVisibility(Visibility.Collapsed)
                    VisualStateManager.GoToState(Me, "hideBuffering", True)
                End If
            End If
        End Sub

        ''' <summary>
        ''' Update the playback position slider and playback position text display with current media position
        ''' </summary>
        Private Sub UpdatePlaybackPosition()
            ' Don't update position based on media element while the user is dragging the slider
            If m_sliderPosition Is Nothing OrElse (Not m_sliderPosition.IsDragging) Then
                ' while playing or paused -- get position from the media element
                Dim position As Double = 0.0
                Select Case m_mediaElement.CurrentState
                    Case MediaElementState.Playing, MediaElementState.Paused, MediaElementState.Buffering
                        position = m_mediaElement.Position.TotalSeconds
                    Case Else
                End Select

                SetPlaybackPosition(position)
            End If
        End Sub

        ''' <summary>
        ''' If chapter list is visible -- Update selected chapter
        ''' Set Selection onto matching chapterlist item
        ''' </summary>
        Private Sub UpdateChapterListbox()
            If (m_selectorChapters IsNot Nothing) AndAlso (m_selectorChapters.Visibility = Visibility.Visible) AndAlso (m_selectorChapters.SelectedIndex <> m_currentChapterIndex) AndAlso (m_currentChapterIndex >= 0) AndAlso (m_currentChapterIndex < m_selectorChapters.Items.Count) Then
                RemoveHandler m_selectorChapters.SelectionChanged, AddressOf OnListBoxSelectionChangedChapters ' avoid clicked-on selection change logic
                m_selectorChapters.SelectedIndex = m_currentChapterIndex
                AddHandler m_selectorChapters.SelectionChanged, AddressOf OnListBoxSelectionChangedChapters

                ' Scroll current chapter into view
                Dim objCurrentChapterItem As Object = m_selectorChapters.Items(m_currentChapterIndex)
                If objCurrentChapterItem IsNot Nothing Then
                    Try
                        If TypeOf m_selectorChapters Is ListBox Then
                            CType(m_selectorChapters, ListBox).ScrollIntoView(objCurrentChapterItem)
                        End If
                    Catch nre As NullReferenceException
                        Debug.WriteLine(nre.ToString())
                        Debug.WriteLine(nre.StackTrace.ToString())
                    End Try
                End If
            End If
        End Sub

        ''' <summary>
        ''' If the Playlist is visible -- Update selected item
        ''' Set Selection onto matching Playlist item
        ''' </summary>
        Private Sub UpdatePlaylistListbox()
            If (m_selectorPlaylist IsNot Nothing) AndAlso (m_selectorPlaylist.Visibility = Visibility.Visible) AndAlso (m_selectorPlaylist.SelectedIndex <> m_currentPlaylistIndex) AndAlso (m_currentPlaylistIndex >= 0) AndAlso (m_currentPlaylistIndex < Playlist.Items.Count) AndAlso (m_currentPlaylistIndex < m_selectorPlaylist.Items.Count) Then
                RemoveHandler m_selectorPlaylist.SelectionChanged, AddressOf OnListBoxSelectionChangedPlaylist ' avoid clicked-on selection change logic
                m_selectorPlaylist.SelectedIndex = m_currentPlaylistIndex
                AddHandler m_selectorPlaylist.SelectionChanged, AddressOf OnListBoxSelectionChangedPlaylist

                ' Scroll current playlist item into view
                Dim objCurrentPlaylistItem As Object = m_selectorPlaylist.Items(m_currentPlaylistIndex)
                If objCurrentPlaylistItem IsNot Nothing Then
                    Try
                        If TypeOf m_selectorChapters Is ListBox Then
                            CType(m_selectorChapters, ListBox).ScrollIntoView(objCurrentPlaylistItem)
                        End If
                    Catch nre As NullReferenceException
                        Debug.WriteLine(nre.ToString())
                        Debug.WriteLine(nre.StackTrace.ToString())
                    End Try
                End If
            End If
        End Sub

        ''' <summary>
        ''' Update the download progress bar as needed.
        ''' </summary>
        Private Sub UpdateDownloadProgress()
            If m_downloadProgressNeedsUpdating Then
                m_downloadProgressNeedsUpdating = False
                Dim downloadProgress As Double = m_mediaElement.DownloadProgress
                SetDownloadPercent(downloadProgress * ProgressConst.Progress2Percent)
                SetDownloadOffsetPercent(m_mediaElement.DownloadProgressOffset * ProgressConst.Progress2Percent)
            End If
        End Sub

        ''' <summary>
        ''' Restore play state after dragging slider position.
        ''' </summary>
        Private Sub RestorePlayStateAfterSliderDrag()
            If m_sliderPosition IsNot Nothing Then
                If (Not m_sliderPosition.IsDragging) AndAlso m_inPlayStateBeforeSliderPositionDrag Then
                    m_inPlayStateBeforeSliderPositionDrag = False
                    InternalPlay()
                End If
            End If
        End Sub

        ''' <summary>
        ''' Event handler for a Timer tick.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnTimerTick(ByVal sender As Object, ByVal e As EventArgs)
            If m_mediaElement Is Nothing Then
                Return
            End If

            If m_goToItemOnNextTick Then
                m_goToItemOnNextTick = False
                GoToPlaylistItem(m_goToItemOnNextTickIndex)
            End If

            If m_seekOnNextTick Then
                DoActualSeek()
            End If

            UpdatePlaybackPosition()
            UpdateChapterListbox()
            UpdatePlaylistListbox()
            UpdatePrevNext()
            UpdateDownloadProgress()
            UpdateBufferingControls()
            RestorePlayStateAfterSliderDrag()
#If DEBUG Then
            If Me.nextTickErrorMessage IsNot Nothing Then
                Me.ShowErrorMessage(Me.nextTickErrorMessage)
                Me.nextTickErrorMessage = Nothing
            End If
#End If
        End Sub

        ''' <summary>
        ''' Event handler for the control fade out event.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnTimerControlFadeOutTick(ByVal sender As Object, ByVal e As EventArgs)
            If currentControlState <> desiredControlState Then
                GoToControlState(desiredControlState)
            End If

            SetDesiredControlState()
        End Sub

        ''' <summary>
        ''' Event handler for the slider position changed event.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnSliderPositionChanged(ByVal sender As Object, ByVal e As RoutedEventArgs)
            SeekToTime(m_sliderPosition.Value)
        End Sub

        ''' <summary>
        ''' Handle DragStart event.
        ''' </summary>
        ''' <param name="sender">Source object, Thumb.</param>
        ''' <param name="e">Drag start args.</param>
        Private Sub OnSliderPositionDragStarted(ByVal sender As Object, ByVal e As DragStartedEventArgs)
            If m_inPlayState Then
                m_inPlayStateBeforeSliderPositionDrag = True
                InternalPause()
            End If
        End Sub

        ''' <summary>
        ''' overridable, item has started
        ''' </summary>
        Public Overridable Sub OnItemStarted()
        End Sub

        ''' <summary>
        ''' Handle DragCompleted event.
        ''' </summary>.
        ''' <param name="sender">Source object, Thumb.</param>
        ''' <param name="e">Drag completed args.</param>
        Private Sub OnSliderPositionDragCompleted(ByVal sender As Object, ByVal e As DragCompletedEventArgs)
            SeekToTime(m_sliderPosition.Value)
            DoActualSeek()
        End Sub

        ''' <summary>
        ''' Event handler for the media element state changed event.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnMediaElementCurrentStateChanged(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim currentState As MediaElementState = m_mediaElement.CurrentState
#If DEBUG Then
            Debug.WriteLine("OnMediaElementCurrentStateChanged new state=" & currentState.ToString())
#End If

            ' Update the state of the play/pause button
            Select Case currentState
                Case MediaElementState.Playing, MediaElementState.Opening, MediaElementState.Buffering, MediaElementState.AcquiringLicense
                    If m_buttonPlayPause IsNot Nothing Then
                        m_buttonPlayPause.IsChecked = True
                    End If
                    Exit Select

                Case MediaElementState.Paused, MediaElementState.Stopped, MediaElementState.Closed
                    If m_buttonPlayPause IsNot Nothing Then
                        m_buttonPlayPause.IsChecked = False
                    End If
                    Exit Select

                Case Else
            End Select

            ' update the state of the thumbnail downloader to avoid taxing network bandwidth when the ME needs bandwidth
            Select Case currentState
                Case MediaElementState.Playing, MediaElementState.Paused, MediaElementState.Stopped, MediaElementState.Closed
                    If Not IsDesignTime Then
                        ThumbnailDownloader.EnableThumbnailDownload()
                    End If
                Case Else
                    ThumbnailDownloader.DisableThumbnailDownload()
            End Select

            UpdateBufferingControls()

            RaiseEvent StateChanged(sender, e)
        End Sub

        ''' <summary>
        ''' Event handler for the media opened event from the media element.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnMediaElementMediaOpened(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ShowErrorMessage(Nothing)
#If DEBUG Then
            Debug.WriteLine("OnMediaElementMediaOpened")
#End If

            ' hide the "start" button if present and visible
            If m_buttonStart IsNot Nothing AndAlso m_buttonStart.Visibility <> Visibility.Collapsed Then
                m_buttonStart.Visibility = Visibility.Collapsed
            End If

            DisplayPoster(-1)
            PerformResize()

            SetPlaybackDuration(m_mediaElement.NaturalDuration.TimeSpan.TotalSeconds)
            UpdateDurationDisplay()
            UpdateCanStep()

            ' Start playing or Pausing the item depending on user settings and current play state.
            If m_inPlayState OrElse Playlist.AutoPlay Then
                InternalPlay()
            ElseIf Playlist.AutoLoad Then
                InternalPause()
            End If
        End Sub

        ''' <summary>
        ''' Event handler for the media ended event from the media element.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnMediaElementMediaEnded(ByVal sender As Object, ByVal e As RoutedEventArgs)
#If DEBUG Then
            Debug.WriteLine("OnMediaElementMediaEnded")
#End If
            GoToPlaylistItemOnNextTick(m_currentPlaylistIndex + 1)
        End Sub

        ''' <summary>
        ''' Event handler for the media failed event from the media element.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnMediaElementMediaFailed(ByVal sender As Object, ByVal e As ExceptionRoutedEventArgs)
#If DEBUG Then
            Debug.WriteLine("OnMediaElementMediaFailed")
#End If

            ' Work-around the fact that silverlight doesn't do any http retries when 404 is returned on 1st attempt -- but a number of CDNs do exactly that on the 1st attempt
            m_mediaFailureRetryCount += 1

            If (m_mediaFailureRetryCount < maxMediaFailureRetries) AndAlso ((Not Me.IsOffline)) AndAlso (Playlist IsNot Nothing) AndAlso (m_currentPlaylistIndex >= 0) AndAlso (Playlist.Items.Count > m_currentPlaylistIndex) Then ' With offline playback -- either the isolated storage file is there or it isn't -- no point in retrying
                Dim playlistItem As PlaylistItem = Playlist.Items(m_currentPlaylistIndex)
                If Not playlistItem.IsAdaptiveStreaming Then ' SmoothStreaming has it's own retry logic -- don't be redundant
                    ShowErrorMessage(m_mediaFailureRetryCount.ToString(CultureInfo.CurrentCulture))
                    DoOpenPlaylistItem(playlistItem)
                    Return
                End If
            End If

            Dim strErrorMessage As String = String.Empty

            If m_mediaElement.Source IsNot Nothing Then
                strErrorMessage = String.Format(CultureInfo.CurrentUICulture, ExpressionMediaPlayer.Resources.errorInvalidMedia, m_mediaElement.Source.ToString())
            ElseIf m_currentPlaylistIndex >= 0 AndAlso m_currentPlaylistIndex < Playlist.Items.Count Then
                strErrorMessage = String.Format(CultureInfo.CurrentUICulture, ExpressionMediaPlayer.Resources.errorInvalidMedia, Playlist.Items(m_currentPlaylistIndex).MediaSource.ToString())
            End If

            If m_currentPlaylistIndex >= 0 AndAlso m_currentPlaylistIndex < Playlist.Items.Count AndAlso Playlist.Items(m_currentPlaylistIndex).IsAdaptiveStreaming Then
                strErrorMessage = String.Format(CultureInfo.CurrentUICulture, ExpressionMediaPlayer.Resources.errorNonSmoothStreamingServer, strErrorMessage)
            End If

            strErrorMessage &= vbCrLf & e.ErrorException.Message.ToString()

#If DEBUG Then
            Debug.WriteLine("Failure message=" & strErrorMessage)
#End If

            ShowErrorMessage(strErrorMessage)

        End Sub

        ''' <summary>
        ''' Event handler for the mouse down event from the media element.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnMediaElementMouseDown(ByVal sender As Object, ByVal e As RoutedEventArgs)
            If (Date.Now.Subtract(m_lastMediaElementClick)).TotalMilliseconds < 300 Then
                TogglePlayPause()
                ToggleFullScreen()
            Else
                TogglePlayPause()
            End If

            m_lastMediaElementClick = Date.Now
        End Sub

        ''' <summary>
        ''' Event handler for the mouse moved event from the stretch box.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnStretchBoxMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
            If Application.Current.Host.Content.IsFullScreen Then
                GoToControlState(ExitFullScreen)
            End If
        End Sub

        ''' <summary>
        ''' Event handler for the download progress event from the media element.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnMediaElementDownloadProgressChanged(ByVal sender As Object, ByVal e As RoutedEventArgs)
#If DEBUG Then
            Debug.WriteLine("OnMediaElementDownloadProgressChanged:" & e.ToString())
#End If
            m_downloadProgressNeedsUpdating = True
        End Sub

        ''' <summary>
        ''' Click handler for the start button.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickStart(ByVal sender As Object, ByVal e As RoutedEventArgs)
            GoToPlaylistItem(0)
            InternalPlay()
        End Sub

        ''' <summary>
        ''' Click handler for the offline button.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickOffline(ByVal sender As Object, ByVal e As RoutedEventArgs)
            If Application.Current.InstallState = InstallState.NotInstalled Then
                If Playlist.ContentNeedsDownloading() Then
                    Dim enoughSpace As Boolean = Playlist.EnsureStorageSpace()
                    If enoughSpace Then
                        EnqueueTakeContentOffline()
                    End If
                End If
            End If
        End Sub

        ''' <summary>
        ''' Click handler for the popout button.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickPopOut(ByVal sender As Object, ByVal e As RoutedEventArgs)
            If HtmlPage.IsEnabled AndAlso HtmlPage.Document IsNot Nothing AndAlso HtmlPage.IsPopupWindowAllowed Then
                PopOutWindow()
            End If
        End Sub

        ''' <summary>
        ''' Click handler for the PlugIn button.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickPlugIn(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim toggleButton As ToggleButton = CType(sender, ToggleButton)
            ShowPlugIn(CBool(toggleButton.IsChecked = True))
        End Sub

        ''' <summary>
        ''' show/hide plugin control
        ''' </summary>
        ''' <param name="show">true for show, false for hide</param>
        <ScriptableMember()> _
        Public Overridable Sub ShowPlugIn(ByVal show As Boolean)
            VisualStateManager.GoToState(Me, (If((show), "showPlugIn", "hidePlugIn")), True)
        End Sub

        ''' <summary>
        ''' Click handler for the play and pause button.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickPlayPause(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' If the big "play" button is shown -- restart playback from the 1st item in the playlist 
            If m_buttonStart IsNot Nothing AndAlso m_buttonStart.Visibility = Visibility.Visible Then
                OnButtonClickStart(sender, e)
                Return
            End If

            TogglePlayPause()
        End Sub

        ''' <summary>
        ''' Click handler for the stop button.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickStop(ByVal sender As Object, ByVal e As RoutedEventArgs)
            m_inPlayState = False
            m_currentPlaylistIndex = 0
            m_currentChapterIndex = 0
            DisplayPoster(m_currentPlaylistIndex)
            If m_mediaElement IsNot Nothing Then
                m_mediaElement.Stop()
                m_mediaElement.AutoPlay = False
                m_mediaElement.Source = Nothing
            End If

            If m_buttonStart IsNot Nothing Then
                m_buttonStart.Visibility = Visibility.Visible
            End If
        End Sub

        ''' <summary>
        ''' Click handler for the previous button.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickPrevious(ByVal sender As Object, ByVal e As RoutedEventArgs)
            SeekToPreviousItem()
        End Sub

        ''' <summary>
        ''' Click handler for the next button.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickNext(ByVal sender As Object, ByVal e As RoutedEventArgs)
            SeekToNextItem()
        End Sub

        ''' <summary>
        ''' seek player to start of current playlistitem
        ''' </summary>
        <ScriptableMember()> _
        Public Overridable Sub SeekToStart()
            If (m_currentPlaylistIndex >= 0) AndAlso (m_currentPlaylistIndex < Playlist.Items.Count) AndAlso (Playlist.Items(m_currentPlaylistIndex) IsNot Nothing) AndAlso (Playlist.Items(m_currentPlaylistIndex).SmpteFrameRate <> SmpteFrameRate.Unknown) Then
                ' Pause playback -- frame step mode isn't very useful otherwise.
                If m_inPlayState Then
                    InternalPause()
                End If

                SeekToTime(New TimeCode("00:00:00:00", Playlist.Items(m_currentPlaylistIndex).SmpteFrameRate).TotalSeconds)
            End If
        End Sub

        ''' <summary>
        ''' seek player to end of current playlist item
        ''' </summary>
        <ScriptableMember()> _
        Public Overridable Sub SeekToEnd()
            If (m_currentPlaylistIndex >= 0) AndAlso (m_currentPlaylistIndex < Playlist.Items.Count) AndAlso (Playlist.Items(m_currentPlaylistIndex) IsNot Nothing) AndAlso (Playlist.Items(m_currentPlaylistIndex).SmpteFrameRate <> SmpteFrameRate.Unknown) Then
                ' Pause playback -- frame step mode isn't very useful otherwise.
                If m_inPlayState Then
                    InternalPause()
                End If
                SeekToTime(m_mediaElement.NaturalDuration.TimeSpan.TotalSeconds)
            End If
        End Sub

        ''' <summary>
        ''' scriptable access to volume
        ''' </summary>
        <ScriptableMember(), Category("Media"), Description("Set player volume 0.0(off) to 1.0(full)")> _
        Public Overridable Property Volume() As Double
            Get
                Return m_mediaElement.Volume
            End Get
            Set(ByVal value As Double)
                UnMuteAt(value)
            End Set
        End Property

        ''' <summary>
        ''' Scriptable access to Mute
        ''' </summary>
        <ScriptableMember(), System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never)> _
        Public Overridable Property Mute() As Boolean
            Get
                Return m_mediaElement.Volume = 0.0F
            End Get
            Set(ByVal value As Boolean)
                If value = True Then
                    CacheVolumeLevel()
                Else
                    UnMute()
                End If
            End Set
        End Property

        ''' <summary>
        ''' Increments the volume by the given amount.
        ''' </summary>
        ''' <param name="dblVolumeIncrement">Amount to increment the volume.</param>
        Private Sub VolumeIncrement(ByVal dblVolumeIncrement As Double)
            Dim dblVolume As Double = m_mediaElement.Volume
            dblVolume = Math.Min(1.0, Math.Max(0.0, dblVolume + dblVolumeIncrement))
            UnMuteAt(dblVolume)
        End Sub

        ''' <summary>
        ''' Click handler for the volume down button.
        ''' </summary>
        ''' <param name="sender">Source of this event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickVolumeDown(ByVal sender As Object, ByVal e As RoutedEventArgs)
            If m_sliderVolume IsNot Nothing Then
                VolumeIncrement(-m_sliderVolume.SmallChange)
            Else
                VolumeIncrement(-0.1)
            End If
        End Sub

        ''' <summary>
        ''' Click handler for the volume up button.
        ''' </summary>
        ''' <param name="sender">Source of this event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickVolumeUp(ByVal sender As Object, ByVal e As RoutedEventArgs)
            If m_sliderVolume IsNot Nothing Then
                VolumeIncrement(m_sliderVolume.SmallChange)
            Else
                VolumeIncrement(0.1)
            End If
        End Sub

        ''' <summary>
        ''' Event handler for the volume changed event.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnSliderVolumeChanged(ByVal sender As Object, ByVal e As RoutedEventArgs)
            UnMuteAt(m_sliderVolume.Value)
        End Sub

        ''' <summary>
        ''' Unmute the volume.
        ''' </summary>
        ''' <returns>Returns true.</returns>
        Private Sub UnMuteAt(ByVal unmutedVolume As Double)
            If (m_buttonMute IsNot Nothing) AndAlso ((m_buttonMute.IsChecked Is Nothing) OrElse (True = m_buttonMute.IsChecked)) Then
                m_buttonMute.IsChecked = False
            End If

            UnCacheVolumeLevelAt(unmutedVolume)
        End Sub

        ''' <summary>
        ''' Unmute the volume.
        ''' </summary>
        Private Sub UnMute()
            UnMuteAt(Double.NaN)
        End Sub

        ''' <summary>
        ''' Caches the current volume level.
        ''' </summary>
        Private Sub CacheVolumeLevel()
            m_mutedCache = True

            If m_mediaElement IsNot Nothing Then
                m_dblUnMutedVolume = m_mediaElement.Volume
                m_mediaElement.Volume = 0.0
            End If
            If m_sliderVolume IsNot Nothing Then
                RemoveHandler m_sliderVolume.ValueChanged, AddressOf OnSliderVolumeChanged
                m_sliderVolume.Value = 0.0
                AddHandler m_sliderVolume.ValueChanged, AddressOf OnSliderVolumeChanged
            End If

            If (m_buttonMute IsNot Nothing) AndAlso ((m_buttonMute.IsChecked Is Nothing) OrElse (False = m_buttonMute.IsChecked)) Then
                m_buttonMute.IsChecked = True
            End If
        End Sub

        ''' <summary>
        ''' Uncaches the current volume level with a supplied volume level
        ''' </summary>
        Private Sub UnCacheVolumeLevelAt(ByVal unmutedVolume As Double)
            m_mutedCache = False

            If m_dblUnMutedVolume < VolumeMuteThreshold Then
                m_dblUnMutedVolume = VolumeDefault
            End If

            If Not Double.IsNaN(unmutedVolume) Then
                m_dblUnMutedVolume = unmutedVolume
            End If

            If m_mediaElement IsNot Nothing Then
                m_mediaElement.Volume = m_dblUnMutedVolume
            End If
            If m_sliderVolume IsNot Nothing Then
                RemoveHandler m_sliderVolume.ValueChanged, AddressOf OnSliderVolumeChanged
                m_sliderVolume.Value = m_dblUnMutedVolume
                AddHandler m_sliderVolume.ValueChanged, AddressOf OnSliderVolumeChanged
            End If
        End Sub

        ''' <summary>
        ''' Uncaches the current volume level.
        ''' </summary>
        Private Sub UnCacheVolumeLevel()
            UnCacheVolumeLevelAt(Double.NaN)
        End Sub

        ''' <summary>
        ''' Click handler for the Mute button.
        ''' </summary>
        ''' <param name="sender">Source of the event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickMute(ByVal sender As Object, ByVal e As RoutedEventArgs)
            If m_buttonMute.IsChecked = False AndAlso (m_mutedCache OrElse m_mediaElement.Volume < VolumeMuteThreshold) Then
                UnCacheVolumeLevel()
            Else
                CacheVolumeLevel()
            End If
        End Sub

        ''' <summary>
        ''' Click handler for the closed captions button.
        ''' </summary>
        ''' <param name="sender">Source of this event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickClosedCaptions(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim toggleButton As ToggleButton = CType(sender, ToggleButton)
            CaptionsVisibility = If((toggleButton.IsChecked = True), Visibility.Collapsed, Visibility.Visible)
        End Sub

        ''' <summary>
        ''' step one frame forwards
        ''' </summary>
        Private Sub StepForwards()
            If (m_currentPlaylistIndex >= 0) AndAlso (m_currentPlaylistIndex < Playlist.Items.Count) AndAlso (Playlist.Items(m_currentPlaylistIndex) IsNot Nothing) AndAlso (Playlist.Items(m_currentPlaylistIndex).SmpteFrameRate <> SmpteFrameRate.Unknown) AndAlso (m_mediaElement.Position.TotalSeconds < m_mediaElement.NaturalDuration.TimeSpan.TotalSeconds) Then
                ' Pause playback -- frame step mode isn't very useful otherwise.
                If m_inPlayState Then
                    InternalPause()
                End If

                Dim oneFrame As New TimeCode("00:00:00:01", Playlist.Items(m_currentPlaylistIndex).SmpteFrameRate)
                Dim current As TimeCode = TimeCode.FromAbsoluteTime(CType(m_mediaElement.Position.TotalSeconds, Double), Playlist.Items(m_currentPlaylistIndex).SmpteFrameRate)
                Dim newPosition As TimeCode = current.Add(oneFrame)
                SeekToTime(newPosition.TotalSeconds)
            End If
        End Sub

        ''' <summary>
        ''' step one frame backwards
        ''' </summary>
        Private Sub StepBackwards()
            If (m_currentPlaylistIndex >= 0) AndAlso (m_currentPlaylistIndex < Playlist.Items.Count) AndAlso (Playlist.Items(m_currentPlaylistIndex) IsNot Nothing) AndAlso (Playlist.Items(m_currentPlaylistIndex).SmpteFrameRate <> SmpteFrameRate.Unknown) AndAlso (m_mediaElement.Position.TotalSeconds > 0) Then
                ' Pause playback -- frame step mode isn't very useful otherwise.
                If m_inPlayState Then
                    InternalPause()
                End If

                Dim oneFrame As New TimeCode("00:00:00:01", Playlist.Items(m_currentPlaylistIndex).SmpteFrameRate)

                Dim current As TimeCode = TimeCode.FromAbsoluteTime(CType(m_mediaElement.Position.TotalSeconds, Double), Playlist.Items(m_currentPlaylistIndex).SmpteFrameRate)

                Dim newPosition As TimeCode
                Try
                    newPosition = current.Subtract(oneFrame)
                Catch e1 As OverflowException
                    newPosition = New TimeCode("00:00:00:00", Playlist.Items(m_currentPlaylistIndex).SmpteFrameRate)

                End Try

                SeekToTime(newPosition.TotalSeconds)
            End If
        End Sub

        ''' <summary>
        ''' Event handler for the step forwards event.
        ''' </summary>
        ''' <param name="sender">Source of this event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonStepForwards(ByVal sender As Object, ByVal e As RoutedEventArgs)
            StepForwards()
        End Sub

        ''' <summary>
        ''' Event handler for the step backwards button.
        ''' </summary>
        ''' <param name="sender">Source of this event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonStepBackwards(ByVal sender As Object, ByVal e As RoutedEventArgs)
            StepBackwards()
        End Sub

        ''' <summary>
        ''' fixup for frame accuracy. Always step forward one frame.
        ''' </summary>
        Private Sub FixupFrameStep()
            If Playlist.DisplayTimeCode AndAlso CType(GetValue(FrameStepVisibilityProperty), Visibility) = Visibility.Visible Then
                StepForwards()
            End If
        End Sub

        ''' <summary>
        ''' Event handler for the resized event.
        ''' </summary>
        ''' <param name="sender">Source of this event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnResized(ByVal sender As Object, ByVal e As EventArgs)
            PerformResize()
        End Sub

        ''' <summary>
        ''' Event handler for Application Exit event.
        ''' </summary>
        ''' <param name="sender">Source of this event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnAppExit(ByVal sender As Object, ByVal e As EventArgs)
            Me.Stop()
        End Sub

        ''' <summary>
        ''' Event handler for the full screen changed event.
        ''' </summary>
        ''' <param name="sender">Source of this event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnFullScreenChanged(ByVal sender As Object, ByVal e As EventArgs)
            PerformResize()
        End Sub

        ''' <summary>
        ''' Click handler for the full screen button.
        ''' </summary>
        ''' <param name="sender">Source of this event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnButtonClickFullScreen(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ToggleFullScreen()
        End Sub

        ''' <summary>
        ''' Event handler for changing the playlist item.
        ''' </summary>
        ''' <param name="sender">Source of this event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnListBoxSelectionChangedPlaylist(ByVal sender As Object, ByVal e As RoutedEventArgs)
            If m_selectorPlaylist IsNot Nothing AndAlso GetHideOnClick(m_selectorPlaylist) Then
                If m_buttonPlaylist IsNot Nothing Then
                    m_buttonPlaylist.IsChecked = False
                End If

                VisualStateManager.GoToState(Me, "hidePlaylist", True)
            End If

            GoToPlaylistItem(m_selectorPlaylist.SelectedIndex)
        End Sub

        ''' <summary>
        ''' Event handler for the chapters item changed event.
        ''' </summary>
        ''' <param name="sender">Source of this event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnListBoxSelectionChangedChapters(ByVal sender As Object, ByVal e As RoutedEventArgs)
            If m_selectorChapters IsNot Nothing AndAlso GetHideOnClick(m_selectorChapters) Then
                If m_buttonChapter IsNot Nothing Then
                    m_buttonChapter.IsChecked = False
                End If

                VisualStateManager.GoToState(Me, "hideChapters", True)
            End If

            m_currentChapterIndex = m_selectorChapters.SelectedIndex
            If (m_currentPlaylistIndex >= 0) AndAlso (m_currentPlaylistIndex < Playlist.Items.Count) AndAlso (m_currentChapterIndex >= 0) AndAlso (m_currentChapterIndex < Playlist.Items(m_currentPlaylistIndex).Chapters.Count) Then
                SeekToTime(Playlist.Items(m_currentPlaylistIndex).Chapters(m_currentChapterIndex).Position)
            End If
        End Sub

        ''' <summary>
        ''' Event handler for the marker reached event from the media element.
        ''' </summary>
        ''' <param name="sender">Source of this event.</param>
        ''' <param name="e">Event args.</param>
        Private Sub OnMediaElementMarkerReached(ByVal sender As Object, ByVal e As TimelineMarkerRoutedEventArgs)
#If DEBUG Then
            Debug.WriteLine("OnMediaElementMarkerReached:" & e.ToString())
#End If
            'Test if this a "Marker" vs a "Caption"
            If (m_selectorChapters IsNot Nothing) AndAlso e.Marker.Type.Equals(MarkerType) Then
                ' compute current chapter index from playback position
                m_currentChapterIndex = ChapterIndexFromPosition(e.Marker.Time)
            End If

            ' Display marker or caption text in the caption area
            Dim type As String = e.Marker.Type.ToUpper(CultureInfo.InvariantCulture)
            If type.Equals(CaptionType) Then
                SetCaptionText(e.Marker.Text)
            End If

            If MarkerReachedEvent IsNot Nothing Then
                Dim timelineMarkerEventArgs As New ScriptableTimelineMarkerRoutedEventArgs(New ScriptableTimelineMarker(e.Marker))
                RaiseEvent MarkerReached(sender, timelineMarkerEventArgs)
            End If
        End Sub

        ''' <summary>
        ''' Helper routine for playing the current item.
        ''' </summary>
        Private Sub InternalPlay()
            If m_buttonStart IsNot Nothing Then
                m_buttonStart.Visibility = Visibility.Collapsed
            End If

            If m_mediaElement IsNot Nothing Then
                ' within 1 second of end of play of last item in list? restart...
                If (PlaybackDuration < 0) AndAlso (Position > (PlaybackDuration - 1.0)) AndAlso (CurrentPlaylistIndex = (Playlist.Items.Count - 1)) Then
                    GoToPlaylistItem(0)
                End If

                m_mediaElement.Play()
                m_inPlayState = True
            End If
        End Sub

        ''' <summary>
        ''' Helper routine for pausing the current item.
        ''' </summary>
        Private Sub InternalPause()
            If m_buttonStart IsNot Nothing Then
                m_buttonStart.Visibility = Visibility.Collapsed
            End If

            If m_mediaElement IsNot Nothing Then
                m_mediaElement.Pause()
            End If

            m_inPlayState = False
        End Sub

#End Region


#Region "ProtectedUtilities"

        ''' <summary>
        ''' Updates the position display.
        ''' </summary>
        Protected Overridable Sub UpdatePositionDisplay()
            Dim frameRate As SmpteFrameRate = SmpteFrameRate.Unknown
            If Playlist.DisplayTimeCode Then
                If (m_currentPlaylistIndex >= 0) AndAlso (m_currentPlaylistIndex < Playlist.Items.Count) Then
                    frameRate = Playlist.Items(m_currentPlaylistIndex).SmpteFrameRate
                End If
            End If

            SetPlaybackPositionText(TimeCode.ConvertToString(PlaybackPosition, frameRate))
        End Sub

        ''' <summary>
        ''' Updates the duration display.
        ''' </summary>
        Protected Overridable Sub UpdateDurationDisplay()
            Dim frameRate As SmpteFrameRate = SmpteFrameRate.Unknown
            If Playlist.DisplayTimeCode Then
                If (m_currentPlaylistIndex >= 0) AndAlso (m_currentPlaylistIndex < Playlist.Items.Count) Then
                    frameRate = Playlist.Items(m_currentPlaylistIndex).SmpteFrameRate
                End If
            End If

            SetPlaybackDurationText(TimeCode.ConvertToString(PlaybackDuration, frameRate))
        End Sub

#If DEBUG Then
        Private nextTickErrorMessage As String = Nothing
        Friend Sub SetNextTickErrorMessage(ByVal message As String)
            If Not String.IsNullOrEmpty(Me.nextTickErrorMessage) Then
                nextTickErrorMessage &= vbCrLf
                nextTickErrorMessage &= message
            Else
                nextTickErrorMessage = message
            End If
        End Sub
#End If
        ''' <summary>
        ''' Shows an error message.
        ''' </summary>
        ''' <param name="message">The error message to display.</param>
        Protected Overridable Sub ShowErrorMessage(ByVal message As String)
            If String.IsNullOrEmpty(message) Then
                SetValue(ErrorMessageVisibilityProperty, Visibility.Collapsed)
                SetValue(ErrorMessageProperty, String.Empty)
                Return
            End If

            SetValue(ErrorMessageProperty, message)
            SetValue(ErrorMessageVisibilityProperty, Visibility.Visible)

            ' if we cant find element for error message use popup...
            If m_elementErrorMessage Is Nothing Then
                If Application.Current.InstallState = InstallState.NotInstalled AndAlso HtmlPage.IsEnabled Then
                    HtmlPage.Window.Alert(message)
                Else
                    MessageBox.Show(message)
                End If
            End If
        End Sub

        ''' <summary>
        ''' Creates a new position timer.
        ''' </summary>
        ''' <param name="interval">Interval of the timer.</param>
        Protected Sub CreatePositionTimer()
            If m_timer Is Nothing Then
                m_timer = New DispatcherTimer()
                m_timer.Interval = New TimeSpan(0, 0, 0, 0, 180) ' approximately 6 NTSC frames - milliseconds - seconds - minutes - hours - days
                AddHandler m_timer.Tick, AddressOf OnTimerTick
            End If

            m_timer.Start()
        End Sub

        ''' <summary>
        ''' Creates the timer for fading out the controls.
        ''' </summary>
        Protected Sub CreateFadeoutTimer()
            If m_timerControlFadeOut Is Nothing Then
                m_timerControlFadeOut = New DispatcherTimer()
                m_timerControlFadeOut.Interval = New TimeSpan(0, 0, 0, 2, 0) ' 2 seconds
                AddHandler m_timerControlFadeOut.Tick, AddressOf OnTimerControlFadeOutTick
            End If

            m_timerControlFadeOut.Start()
        End Sub

        ''' <summary>
        ''' Hooks our event handlers.
        ''' </summary>
        Protected Overridable Sub HookHandlers()
            If Not IsDesignTime Then
                CreatePositionTimer()
                CreateFadeoutTimer()
            End If

            AddHandler KeyDown, AddressOf MediaPlayerKeyDown

            If Application.Current IsNot Nothing Then
                AddHandler Application.Current.Host.Content.FullScreenChanged, AddressOf OnFullScreenChanged
                AddHandler Application.Current.Host.Content.Resized, AddressOf OnResized
                AddHandler Application.Current.Exit, AddressOf OnAppExit
            End If

            If m_mediaElement IsNot Nothing Then
                AddHandler m_mediaElement.MediaFailed, AddressOf OnMediaElementMediaFailed
                AddHandler m_mediaElement.MediaOpened, AddressOf OnMediaElementMediaOpened
                AddHandler m_mediaElement.MediaEnded, AddressOf OnMediaElementMediaEnded
                AddHandler m_mediaElement.CurrentStateChanged, AddressOf OnMediaElementCurrentStateChanged
                AddHandler m_mediaElement.MarkerReached, AddressOf OnMediaElementMarkerReached
                AddHandler m_mediaElement.DownloadProgressChanged, AddressOf OnMediaElementDownloadProgressChanged
                AddHandler m_mediaElement.MouseLeftButtonDown, AddressOf OnMediaElementMouseDown
            End If

            If m_elementStretchBox IsNot Nothing Then
                AddHandler m_elementStretchBox.MouseMove, AddressOf OnStretchBoxMouseMove
            End If

            If m_buttonStart IsNot Nothing Then
                AddHandler m_buttonStart.Click, AddressOf OnButtonClickStart
            End If

            If m_buttonOffline IsNot Nothing Then
                AddHandler m_buttonOffline.Click, AddressOf OnButtonClickOffline
            End If

            If m_buttonPopOut IsNot Nothing Then
                AddHandler m_buttonPopOut.Click, AddressOf OnButtonClickPopOut
            End If

            If m_buttonPlugIn IsNot Nothing Then
                AddHandler m_buttonPlugIn.Click, AddressOf OnButtonClickPlugIn
            End If

            If m_buttonPlayPause IsNot Nothing Then
                AddHandler m_buttonPlayPause.Click, AddressOf OnButtonClickPlayPause
            End If

            If m_buttonStop IsNot Nothing Then
                AddHandler m_buttonStop.Click, AddressOf OnButtonClickStop
            End If

            If m_buttonPrevious IsNot Nothing Then
                AddHandler m_buttonPrevious.Click, AddressOf OnButtonClickPrevious
            End If

            If m_buttonNext IsNot Nothing Then
                AddHandler m_buttonNext.Click, AddressOf OnButtonClickNext
            End If

            If m_buttonMute IsNot Nothing Then
                AddHandler m_buttonMute.Click, AddressOf OnButtonClickMute
            End If

            If m_buttonStepBackwards IsNot Nothing Then
                AddHandler m_buttonStepBackwards.Click, AddressOf OnButtonStepBackwards
            End If

            If m_buttonStepForwards IsNot Nothing Then
                AddHandler m_buttonStepForwards.Click, AddressOf OnButtonStepForwards
            End If

            If m_buttonClosedCaptions IsNot Nothing Then
                AddHandler m_buttonClosedCaptions.Click, AddressOf OnButtonClickClosedCaptions
            End If

            If m_buttonVolumeDown IsNot Nothing Then
                AddHandler m_buttonVolumeDown.Click, AddressOf OnButtonClickVolumeDown
            End If

            If m_buttonVolumeUp IsNot Nothing Then
                AddHandler m_buttonVolumeUp.Click, AddressOf OnButtonClickVolumeUp
            End If

            If m_buttonFullScreen IsNot Nothing Then
                AddHandler m_buttonFullScreen.Click, AddressOf OnButtonClickFullScreen
            End If

            If m_buttonPlaylist IsNot Nothing Then
                AddHandler m_buttonPlaylist.Click, AddressOf OnButtonClickPlaylist
            End If

            If m_selectorPlaylist IsNot Nothing Then
                AddHandler m_selectorPlaylist.SelectionChanged, AddressOf OnListBoxSelectionChangedPlaylist
            End If

            If m_selectorChapters IsNot Nothing Then
                AddHandler m_selectorChapters.SelectionChanged, AddressOf OnListBoxSelectionChangedChapters
            End If

            If m_buttonChapter IsNot Nothing Then
                AddHandler m_buttonChapter.Click, AddressOf OnButtonClickChapter
            End If

            If m_sliderPosition IsNot Nothing Then
                AddHandler m_sliderPosition.ValueChanged, AddressOf OnSliderPositionChanged
                AddHandler m_sliderPosition.DragStarted, AddressOf OnSliderPositionDragStarted
                AddHandler m_sliderPosition.DragCompleted, AddressOf OnSliderPositionDragCompleted
            End If

            If m_sliderVolume IsNot Nothing AndAlso m_mediaElement IsNot Nothing Then
                AddHandler m_sliderVolume.ValueChanged, AddressOf OnSliderVolumeChanged
            End If

            If Not IsDesignTime Then
                ThumbnailDownloader.EnableThumbnailDownload()
            End If
        End Sub

        ''' <summary>
        ''' clicks a button (or toggle button) programmatically if it is showing.
        ''' </summary>
        ''' <param name="buttonBase"></param>
        Private Shared Sub ClickButton(ByVal buttonBase As ButtonBase, ByVal handler As RoutedEventHandler)
            If buttonBase IsNot Nothing AndAlso buttonBase.Visibility = Visibility.Visible Then
                If TypeOf buttonBase Is Button Then
                    Dim buttonAutoPeer As New ButtonAutomationPeer(TryCast(buttonBase, Button))
                    If buttonAutoPeer.IsEnabled() Then
                        Dim invokeProvider As IInvokeProvider = CType(buttonAutoPeer.GetPattern(PatternInterface.Invoke), IInvokeProvider)
                        invokeProvider.Invoke()
                    End If
                ElseIf TypeOf buttonBase Is ToggleButton Then
                    Dim buttonAutoPeer As New ToggleButtonAutomationPeer(TryCast(buttonBase, ToggleButton))
                    If buttonAutoPeer.IsEnabled() Then
                        Dim toggleProvider As IToggleProvider = CType(buttonAutoPeer.GetPattern(PatternInterface.Toggle), IToggleProvider)
                        toggleProvider.Toggle()
                        Debug.Assert(handler IsNot Nothing)
                        handler(CType(buttonBase, ToggleButton), Nothing)
                    End If
                ElseIf TypeOf buttonBase Is RepeatButton Then
                    Dim buttonAutoPeer As New RepeatButtonAutomationPeer(TryCast(buttonBase, RepeatButton))
                    If buttonAutoPeer.IsEnabled() Then
                        Dim invokeProvider As IInvokeProvider = CType(buttonAutoPeer.GetPattern(PatternInterface.Invoke), IInvokeProvider)
                        invokeProvider.Invoke()
                    End If
                End If
            End If
        End Sub

        ''' <summary>
        ''' handle multimedia keyboard controls
        ''' </summary>
        Protected Overridable Sub MediaPlayerKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
            Select Case e.PlatformKeyCode
                Case &HB0 'VK_MEDIA_NEXT_TRACK
                    GoToNextPlaylistItem()
                Case &HB1 'VK_MEDIA_PREV_TRACK
                    GoToPreviousPlaylistItem()
                Case &HB2 'VK_MEDIA_STOP
                    Me.Stop()
                Case &HB3 'VK_MEDIA_PLAY_PAUSE
                    TogglePlayPause()
            End Select

            Select Case e.Key
                Case Key.B
                    ClickButton(m_buttonPrevious, AddressOf OnButtonClickPrevious)
                Case Key.C
                    ClickButton(m_buttonChapter, AddressOf OnButtonClickChapter)
                Case Key.D
                    ClickButton(m_buttonOffline, AddressOf OnButtonClickOffline)
                Case Key.F
                    ToggleFullScreen()
                Case Key.G
                    ClickButton(m_buttonPlugIn, AddressOf OnButtonClickPlugIn)
                Case Key.I
                    ClickButton(m_buttonStepBackwards, AddressOf OnButtonStepBackwards)
                Case Key.J
                    ClickButton(m_buttonVolumeDown, AddressOf OnButtonClickVolumeDown)
                Case Key.K
                    ClickButton(m_buttonVolumeUp, AddressOf OnButtonClickVolumeUp)
                Case Key.L
                    ClickButton(m_buttonClosedCaptions, AddressOf OnButtonClickClosedCaptions)
                Case Key.M
                    ClickButton(m_buttonMute, AddressOf OnButtonClickMute)
                Case Key.N
                    ClickButton(m_buttonNext, AddressOf OnButtonClickNext)
                Case Key.O
                    ClickButton(m_buttonStepForwards, AddressOf OnButtonStepForwards)
                Case Key.P
                    TogglePlayPause()
                Case Key.S
                    ClickButton(m_buttonStop, AddressOf OnButtonClickStop)
                Case Key.V
                    ClickButton(m_buttonPlaylist, AddressOf OnButtonClickPlaylist)
                Case Key.X
                    ClickButton(m_buttonPopOut, AddressOf OnButtonClickPopOut)
                Case Key.Home
                    SeekToStart()
                Case Key.End
                    SeekToEnd()
            End Select
        End Sub

        ''' <summary>
        ''' Unhooks our event handlers.
        ''' </summary>
        Protected Overridable Sub UnhookHandlers()
            If m_mediaElement IsNot Nothing Then
                RemoveHandler m_mediaElement.MediaFailed, AddressOf OnMediaElementMediaFailed
                RemoveHandler m_mediaElement.MediaOpened, AddressOf OnMediaElementMediaOpened
                RemoveHandler m_mediaElement.MediaEnded, AddressOf OnMediaElementMediaEnded
                RemoveHandler m_mediaElement.CurrentStateChanged, AddressOf OnMediaElementCurrentStateChanged
                RemoveHandler m_mediaElement.MarkerReached, AddressOf OnMediaElementMarkerReached
                RemoveHandler m_mediaElement.DownloadProgressChanged, AddressOf OnMediaElementDownloadProgressChanged
                RemoveHandler m_mediaElement.MouseLeftButtonDown, AddressOf OnMediaElementMouseDown
            End If

            If m_elementStretchBox IsNot Nothing Then
                RemoveHandler m_elementStretchBox.MouseMove, AddressOf OnStretchBoxMouseMove
            End If

            If m_buttonStart IsNot Nothing Then
                RemoveHandler m_buttonStart.Click, AddressOf OnButtonClickStart
            End If

            If m_buttonOffline IsNot Nothing Then
                RemoveHandler m_buttonOffline.Click, AddressOf OnButtonClickOffline
            End If

            If m_buttonPopOut IsNot Nothing Then
                RemoveHandler m_buttonPopOut.Click, AddressOf OnButtonClickPopOut
            End If

            If m_buttonPlugIn IsNot Nothing Then
                RemoveHandler m_buttonPlugIn.Click, AddressOf OnButtonClickPlugIn
            End If

            If m_buttonPlayPause IsNot Nothing Then
                RemoveHandler m_buttonPlayPause.Click, AddressOf OnButtonClickPlayPause
            End If

            If m_buttonStop IsNot Nothing Then
                RemoveHandler m_buttonStop.Click, AddressOf OnButtonClickStop
            End If

            If m_buttonPrevious IsNot Nothing Then
                RemoveHandler m_buttonPrevious.Click, AddressOf OnButtonClickPrevious
            End If

            If m_buttonNext IsNot Nothing Then
                RemoveHandler m_buttonNext.Click, AddressOf OnButtonClickNext
            End If

            If m_buttonMute IsNot Nothing Then
                RemoveHandler m_buttonMute.Click, AddressOf OnButtonClickMute
            End If

            If m_buttonClosedCaptions IsNot Nothing Then
                RemoveHandler m_buttonClosedCaptions.Click, AddressOf OnButtonClickClosedCaptions
            End If

            If m_buttonVolumeDown IsNot Nothing Then
                RemoveHandler m_buttonVolumeDown.Click, AddressOf OnButtonClickVolumeDown
            End If

            If m_buttonVolumeUp IsNot Nothing Then
                RemoveHandler m_buttonVolumeUp.Click, AddressOf OnButtonClickVolumeUp
            End If

            If m_buttonFullScreen IsNot Nothing Then
                RemoveHandler m_buttonFullScreen.Click, AddressOf OnButtonClickFullScreen
            End If

            If m_buttonStepBackwards IsNot Nothing Then
                RemoveHandler m_buttonStepBackwards.Click, AddressOf OnButtonStepBackwards
            End If

            If m_buttonStepForwards IsNot Nothing Then
                RemoveHandler m_buttonStepForwards.Click, AddressOf OnButtonStepForwards
            End If

            If m_buttonPlaylist IsNot Nothing Then
                RemoveHandler m_buttonPlaylist.Click, AddressOf OnButtonClickPlaylist
            End If

            If m_selectorPlaylist IsNot Nothing Then
                RemoveHandler m_selectorPlaylist.SelectionChanged, AddressOf OnListBoxSelectionChangedPlaylist
            End If

            If m_selectorChapters IsNot Nothing Then
                RemoveHandler m_selectorChapters.SelectionChanged, AddressOf OnListBoxSelectionChangedChapters
            End If

            If m_buttonChapter IsNot Nothing Then
                RemoveHandler m_buttonChapter.Click, AddressOf OnButtonClickChapter
            End If

            If m_sliderPosition IsNot Nothing Then
                RemoveHandler m_sliderPosition.ValueChanged, AddressOf OnSliderPositionChanged
                RemoveHandler m_sliderPosition.DragStarted, AddressOf OnSliderPositionDragStarted
                RemoveHandler m_sliderPosition.DragCompleted, AddressOf OnSliderPositionDragCompleted
            End If

            If m_sliderVolume IsNot Nothing Then
                RemoveHandler m_sliderVolume.ValueChanged, AddressOf OnSliderVolumeChanged
            End If

            If m_timer IsNot Nothing Then
                m_timer.Stop()
            End If

            ThumbnailDownloader.DisableThumbnailDownload()
        End Sub

        ''' <summary>
        ''' Configures the binding for the playlist control.
        ''' </summary>
        Protected Sub ConfigureBinding()
            If m_selectorPlaylist IsNot Nothing Then
                m_selectorPlaylist.ItemsSource = Me.Playlist.Items
            End If
        End Sub

        ''' <summary>
        ''' Applies our cached properties.
        ''' </summary>
        Protected Overridable Sub ApplyProperties()
            Me.Stop()

            If m_mediaElement IsNot Nothing Then
                m_mediaElement.CacheMode = If((Playlist.EnableCachedComposition AndAlso (m_mediaElement.CacheMode Is Nothing)), New BitmapCache(), Nothing)
            End If

            SetPopOutButtonVisibility(If(Playlist.EnablePopOut AndAlso (Application.Current.InstallState = InstallState.NotInstalled), Visibility.Visible, Visibility.Collapsed))
            SetOfflineButtonVisibility(If(Playlist.EnableOffline AndAlso (Application.Current.InstallState = InstallState.NotInstalled), Visibility.Visible, Visibility.Collapsed))
            SetChaptersButtonVisibility(Visibility.Collapsed)
            SetPlugInButtonVisibility(Visibility.Collapsed)
            SetCaptionsButtonVisibility(If(Playlist.EnableCaptions, Visibility.Visible, Visibility.Collapsed))
            CaptionsVisibility = If(Playlist.EnableCaptions, Visibility.Visible, Visibility.Collapsed)
            Mute = Playlist.StartMuted

            If Playlist IsNot Nothing AndAlso Playlist.Items IsNot Nothing Then
                SetPlaylistButtonVisibility(If(Playlist.Items.Count > 1, Visibility.Visible, Visibility.Collapsed))
                If Playlist.Items.Count <= 1 Then
                    ShowPlaylist(False)
                End If
            End If

            If m_mediaElement IsNot Nothing AndAlso (Playlist.Items.Count > 0) Then
                m_mediaElement.AutoPlay = Playlist.AutoPlay
                If Playlist.AutoLoad OrElse Playlist.AutoPlay Then
                    GoToPlaylistItem(0)
                Else
                    DisplayPoster(0)
                End If
            End If

            If m_buttonStart IsNot Nothing Then
                m_buttonStart.Visibility = If(Playlist.AutoPlay, Visibility.Collapsed, Visibility.Visible)
            End If

            If m_sliderVolume IsNot Nothing AndAlso m_mediaElement IsNot Nothing Then
                m_sliderVolume.Minimum = 0
                m_sliderVolume.Maximum = 1
                m_sliderVolume.SmallChange = 0.1
                m_sliderVolume.LargeChange = 0.2
                If m_mutedCache Then
                    m_sliderVolume.Value = 0.0
                    m_mediaElement.Volume = 0.0
                Else
                    m_sliderVolume.Value = VolumeDefault
                End If

                m_mediaElement.Volume = m_sliderVolume.Value
            End If

            If m_buttonMute IsNot Nothing Then
                m_buttonMute.IsChecked = m_mutedCache
            End If

            PerformResize()
        End Sub

        ''' <summary>
        ''' Adjust the size of the poster image to match the displayed video .
        ''' </summary>
        Protected Overridable Sub AdjustPosterSize(ByVal playlistItemIndex As Integer)
            If (Playlist.StretchMode = System.Windows.Media.Stretch.None) AndAlso ((Not Application.Current.Host.Content.IsFullScreen)) AndAlso (playlistItemIndex >= 0) AndAlso (playlistItemIndex < Playlist.Items.Count) AndAlso (Playlist.Items(playlistItemIndex).VideoWidth > 0) AndAlso (Playlist.Items(playlistItemIndex).VideoHeight > 0) Then
                SetPosterImageMaxWidth(Playlist.Items(playlistItemIndex).VideoWidth)
                SetPosterImageMaxHeight(Playlist.Items(playlistItemIndex).VideoHeight)
            Else
                SetPosterImageMaxWidth(Double.PositiveInfinity)
                SetPosterImageMaxHeight(Double.PositiveInfinity)
            End If
        End Sub

        ''' <summary>
        ''' Displays a poster image for a playlist item.
        ''' </summary>
        ''' <param name="playlistItemIndex">Index of the item to display a poster image for.</param>
        Protected Overridable Sub DisplayPoster(ByVal playlistItemIndex As Integer)
            If playlistItemIndex >= 0 AndAlso playlistItemIndex < Playlist.Items.Count AndAlso Playlist.Items(playlistItemIndex).ThumbSource IsNot Nothing Then
                SetPosterImageSource(Playlist.Items(playlistItemIndex).ThumbSource.ToString())
                AdjustPosterSize(playlistItemIndex)
                VisualStateManager.GoToState(Me, "showPosterFrame", True)
            Else
                VisualStateManager.GoToState(Me, "hidePosterFrame", True)
            End If
        End Sub

        ''' <summary>
        ''' Seeks to the given time.
        ''' </summary>
        ''' <param name="seconds">Time to seek to.</param>
        <ScriptableMember()> _
        Public Overridable Sub SeekToTime(ByVal seconds As Double)
            ' collapse / defer seeks
            m_seekOnNextTick = True
            m_seekOnNextTickPosition = seconds
        End Sub

        ''' <summary>
        ''' Performs the actual seek.
        ''' </summary>
        Protected Overridable Sub DoActualSeek()
            If (Not m_mediaElement.CanSeek) OrElse (Not m_seekOnNextTick) Then
                m_seekOnNextTick = False
                Return
            End If

            ' Don't attempt to seek unless the element is actually playing or paused
            Select Case m_mediaElement.CurrentState
                Case MediaElementState.Playing, MediaElementState.Paused
                Case Else
                    ' Defering while media isn't ready...
                    Return
            End Select

            If m_sliderPosition IsNot Nothing Then
                If m_sliderPosition.IsDragging AndAlso m_currentItemIsAdaptive Then
                    ' Defering Seek during Drag
                    Return
                End If
            End If

            ' Finaly go ahead and seek!
            ClearCaptionText()
            m_seekOnNextTick = False
            Dim seconds As Double = Math.Min(PlaybackDuration, Math.Max(0.0, m_seekOnNextTickPosition))

            Dim newPosition As TimeSpan = TimeSpan.FromSeconds(seconds)
            m_mediaElement.Position = newPosition

            ' update chapter index (listbox selection will be updated in the TimerTick code)
            m_currentChapterIndex = ChapterIndexFromPosition(newPosition)
        End Sub

        ''' <summary>
        ''' Skips forwards or backwards.
        ''' </summary>
        ''' <param name="direction">Direction to skip.</param>
        Protected Overridable Sub SkipTime(ByVal direction As Integer)
            Dim delta As Double = Math.Max(MinDelta, PlaybackDuration / SkipSteps)
            Dim skipbuffer As Double = (delta - MediaPlayer.SkipBuffer)
            Dim newposition As Double = PlaybackPosition + (delta * direction)
            If newposition < -(skipbuffer) AndAlso m_currentPlaylistIndex > 0 Then
                GoToPreviousPlaylistItem()
            ElseIf (newposition > (PlaybackDuration + skipbuffer)) AndAlso (m_currentPlaylistIndex < (Playlist.Items.Count - 1)) Then
                GoToNextPlaylistItem()
            Else
                newposition = Math.Max(0.0, newposition)
                newposition = Math.Min(PlaybackDuration, newposition)
                SeekToTime(newposition)
            End If
        End Sub

        ''' <summary>
        ''' Seeks to the next playlist item.
        ''' </summary>
        <ScriptableMember()> _
        Public Overridable Sub SeekToNextItem()
            If Not SeekToChapterPoint(m_currentChapterIndex + 1) Then
                SkipTime(1)
            End If
        End Sub

        ''' <summary>
        ''' Seeks to the previous playlist item.
        ''' </summary>
        <ScriptableMember()> _
        Public Overridable Sub SeekToPreviousItem()
            If Not SeekToChapterPoint(m_currentChapterIndex - 1) Then
                SkipTime(-1)
            End If
        End Sub

        ''' <summary>
        ''' Finds a chapter index from a position.
        ''' </summary>
        ''' <param name="position">The position we are looking for.</param>
        ''' <returns>The index of the chapter item for this position.</returns>
        Protected Function ChapterIndexFromPosition(ByVal position As TimeSpan) As Integer
            Dim seconds As Double = position.TotalSeconds

            Dim indexChapter As Integer = 0
            If (m_currentPlaylistIndex >= 0) AndAlso (m_currentPlaylistIndex < Playlist.Items.Count) Then
                Do While indexChapter < Playlist.Items(m_currentPlaylistIndex).Chapters.Count AndAlso Playlist.Items(m_currentPlaylistIndex).Chapters(indexChapter).Position < seconds
                    indexChapter += 1
                Loop
            End If

            Return indexChapter
        End Function

        ''' <summary>
        ''' Seeks to a chapter point.
        ''' </summary>
        ''' <param name="chapterIndex">The index of the chapter point to seek to.</param>
        ''' <returns>true if we found the index, false otherwise.</returns>
        <ScriptableMember()> _
        Public Overridable Function SeekToChapterPoint(ByVal chapterIndex As Integer) As Boolean
            If (m_currentPlaylistIndex >= 0) AndAlso (m_currentPlaylistIndex < Playlist.Items.Count) Then
                If (chapterIndex >= 0) AndAlso (chapterIndex < Playlist.Items(m_currentPlaylistIndex).Chapters.Count) Then
                    m_currentChapterIndex = chapterIndex
                    m_selectorChapters.SelectedIndex = m_currentChapterIndex
                    Return True
                End If
            End If

            Return False
        End Function

        ''' <summary>
        ''' Toggles our play/pause state.
        ''' </summary>
        Protected Overridable Sub TogglePlayPause()
            ' Change current play state depending on current state.
            If m_mediaElement IsNot Nothing Then
                Select Case m_mediaElement.CurrentState
                    Case MediaElementState.AcquiringLicense, MediaElementState.Buffering, MediaElementState.Individualizing, MediaElementState.Playing, MediaElementState.Opening
                        InternalPause()
                        FixupFrameStep()
                    Case MediaElementState.Paused
                        'If at end of last playlist item items -- rewind to beginning of 1st item
                        If m_currentPlaylistIndex >= (Playlist.Items.Count - 1) Then
                            Dim positionCurrent As Double = m_mediaElement.Position.TotalSeconds
                            If positionCurrent >= PlaybackDuration Then
                                m_inPlayState = True
                                GoToPlaylistItem(0)
                            End If
                        End If

                        InternalPlay()
                    Case MediaElementState.Closed, MediaElementState.Stopped
                        GoToPlaylistItem(m_currentPlaylistIndex)
                        InternalPlay()
                End Select
            End If
        End Sub


        ''' <summary>
        ''' scripting utility function
        ''' </summary>
        <ScriptableMember()> _
        Public Function CreateUri(ByVal uri As String) As Uri
            Return New Uri(uri, UriKind.RelativeOrAbsolute)
        End Function

#End Region

#Region "PrivateUtilityMethods"

        ''' <summary>
        ''' Goes to our control state.
        ''' </summary>
        ''' <param name="controlState">Control state to go to.</param>
        Private Sub GoToControlState(ByVal controlState As String)
            m_timerControlFadeOut.Stop()
            VisualStateManager.GoToState(Me, controlState, True)
            currentControlState = controlState
            Dim element As FrameworkElement = TryCast(Application.Current.RootVisual, FrameworkElement)
            If element IsNot Nothing Then
                If EnterFullScreen = currentControlState Then
                    element.Cursor = Cursors.None
                ElseIf ExitFullScreen = currentControlState Then
                    element.Cursor = Cursors.Arrow
                End If
            End If

            m_timerControlFadeOut.Start()
        End Sub

        ''' <summary>
        ''' Sets the desired control state.
        ''' </summary>
        Private Sub SetDesiredControlState()
            If (Application.Current IsNot Nothing) Then
                If Application.Current.Host.Content.IsFullScreen Then
                    desiredControlState = EnterFullScreen
                Else
                    desiredControlState = ExitFullScreen
                End If
            End If
        End Sub

        ''' <summary>
        ''' Performs a resize.
        ''' </summary>
        Private Sub PerformResize()
            If (Application.Current IsNot Nothing) Then
                If Application.Current.Host.Content.IsFullScreen Then
                    Me.HorizontalAlignment = HorizontalAlignment.Stretch
                    Me.VerticalAlignment = VerticalAlignment.Stretch
                End If
            End If

            SetDesiredControlState()

            AdjustPosterSize((If((m_currentPlaylistIndex >= 0), (m_currentPlaylistIndex), (0)))) ' special case for adjusting poster size between normal and full screen mode prior to playing the 1st item.

            ApplySizeAndStretch()
        End Sub

        ''' <summary>
        ''' Apply size and stretch mode to the MediaElement
        ''' </summary>
        Private Sub ApplySizeAndStretch()
            If Playlist Is Nothing OrElse m_mediaElement Is Nothing Then
                ' Skip when called during boot up
                Return
            End If

            Dim modeToApply As Stretch = Playlist.StretchMode

            If Application.Current.Host.Content.IsFullScreen Then
                If modeToApply = Stretch.None Then ' override stretchMode for FullScreen if they have none.
                    modeToApply = Stretch.Uniform
                End If
            End If

            ' For "stretchy modes" -- just let mediaElement do it's thing
            If modeToApply <> Stretch.None Then
                m_mediaElement.Width = Double.NaN
                m_mediaElement.Height = Double.NaN
                m_mediaElement.Stretch = modeToApply
                Return
            End If

            Dim widthToSet As Double = Double.NaN ' Auto sizing
            Dim heightToSet As Double = Double.NaN

            If (m_currentPlaylistIndex >= 0) AndAlso (m_currentPlaylistIndex < Playlist.Items.Count) AndAlso (Playlist.StretchMode = Stretch.None) Then
                Dim currentItem = Playlist.Items(m_currentPlaylistIndex)
                If (currentItem.VideoWidth > 0) AndAlso (currentItem.VideoHeight > 0) AndAlso (currentItem.VideoHeight < Short.MaxValue) AndAlso (currentItem.VideoHeight < Short.MaxValue) Then
                    widthToSet = currentItem.VideoWidth
                    heightToSet = currentItem.VideoHeight
                End If

                If Playlist.Items(m_currentPlaylistIndex).IsAdaptiveStreaming AndAlso (modeToApply = Stretch.None) Then
                    modeToApply = Stretch.Uniform
                End If
            End If

            Dim scalingFactor As Double = 1.0

            If m_elementVideoWindow IsNot Nothing AndAlso (Not Double.IsNaN(widthToSet)) Then
                Dim availibleWidth As Double = m_elementVideoWindow.ActualWidth
                Dim availibleHeight As Double = m_elementVideoWindow.ActualHeight

                Dim scalingFactorX As Double = availibleWidth / widthToSet
                Dim scalingFactorY As Double = availibleHeight / heightToSet
                scalingFactor = Math.Min(scalingFactorX, scalingFactorY)
            End If

            If scalingFactor < 1.0 Then
                widthToSet *= scalingFactor
                heightToSet *= scalingFactor

                If modeToApply = Stretch.None Then
                    modeToApply = Stretch.Uniform
                End If
            End If

            m_mediaElement.Width = widthToSet
            m_mediaElement.Height = heightToSet
            m_mediaElement.Stretch = modeToApply
        End Sub

        ''' <summary>
        ''' Clears our caption text.
        ''' </summary>
        Private Sub ClearCaptionText()
            SetCaptionText(String.Empty)
        End Sub

        ''' <summary>
        ''' Updates the frame step property.
        ''' </summary>
        Private Sub UpdateCanStep()
            If IsDesignTime OrElse ((m_mediaElement IsNot Nothing) AndAlso (m_mediaElement.CanSeek) AndAlso (m_currentPlaylistIndex >= 0) AndAlso (m_currentPlaylistIndex < Playlist.Items.Count) AndAlso (Playlist.Items(m_currentPlaylistIndex).SmpteFrameRate <> SmpteFrameRate.Unknown) AndAlso ((Not Playlist.Items(m_currentPlaylistIndex).IsAdaptiveStreaming))) Then 'stepping isn't compatible with AdaptiveStreaming MSS currently
                SetValue(FrameStepVisibilityProperty, Visibility.Visible)
                Return
            End If

            SetValue(FrameStepVisibilityProperty, Visibility.Collapsed)
        End Sub

        ''' <summary>
        ''' Updates the ButtonPreviousIsEnabled and ButtonNextIsEnabled properties.
        ''' </summary>
        Private Sub UpdatePrevNext()
            If m_currentPlaylistIndex >= 0 AndAlso m_currentPlaylistIndex < Playlist.Items.Count Then
                Dim chapterCount As Integer = Playlist.Items(m_currentPlaylistIndex).Chapters.Count
                If chapterCount > 0 Then
                    SetValue(ButtonPreviousIsEnabledProperty, m_currentChapterIndex > 0)
                    SetValue(ButtonNextIsEnabledProperty, m_currentChapterIndex < (chapterCount - 1))
                Else
                    SetValue(ButtonPreviousIsEnabledProperty, PlaybackPosition > 0.0)
                    SetValue(ButtonNextIsEnabledProperty, PlaybackPosition < PlaybackDuration)
                End If
            Else
                SetValue(ButtonPreviousIsEnabledProperty, False)
                SetValue(ButtonNextIsEnabledProperty, False)
            End If
        End Sub

#End Region

#Region "Offline Support"
        ''' <summary>
        ''' offline/online state change
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <param name="e"></param>
        Private Sub PlayerInstallStateChanged(ByVal sender As Object, ByVal e As EventArgs)
            Debug.WriteLine("PlayerInstallStateChanged InstallState=" & Application.Current.InstallState.ToString())
            If Not IsDesignTime Then
                Me.Stop()
                Select Case Application.Current.InstallState
                    Case InstallState.Installed
                        Me.SetOfflineButtonVisibility(Visibility.Collapsed)
                        Me.SetOfflineButtonEnabled(False)
                    Case InstallState.Installing
                        Me.SetOfflineButtonEnabled(False)
                    Case InstallState.InstallFailed, InstallState.NotInstalled
                        IsoUri.ClearIsoStorage()
                        Me.SetOfflineButtonVisibility(Visibility.Visible)
                        Me.SetOfflineButtonEnabled(Playlist.EnableOffline)
                End Select
            End If
        End Sub

        Private Sub EnqueueTakeContentOffline()
            Me.Stop()
            SetOfflineDownloadProgressVisibility(Visibility.Visible)
            SetOfflineButtonEnabled(False)
            Playlist.TakeOffline(Me)
        End Sub

        ''' <summary>
        ''' network went on/off line
        ''' </summary>
        ''' <param name="sender"></param>
        ''' <param name="e"></param>
        Private Sub PlayerNetworkAddressChanged(ByVal sender As Object, ByVal e As EventArgs)
            If (Not NetworkInterface.GetIsNetworkAvailable()) AndAlso Playlist.IsDownloading Then
                IsoUri.ClearIsoStorage()
                ShowErrorMessage(ExpressionMediaPlayer.Resources.errorOfflineInterrupted)
            End If
        End Sub
#End Region

        ''' <summary>
        ''' This class contains constants for progress reporting.
        ''' </summary>
        Private NotInheritable Class ProgressConst
            ''' <summary>
            ''' Maximum progress in the control.
            ''' </summary>
            Public Const MaxProgress As Double = 1.0

            ''' <summary>
            ''' Maximum progress percent.
            ''' </summary>
            Public Const MaxPercent As Double = 100.0

            ''' <summary>
            ''' Converts a progress to a percent.
            ''' </summary>
            Public Const Progress2Percent As Double = 100.0

            ''' <summary>
            ''' Prevents a default instance of the ProgressConst class from being created.
            ''' </summary>
            Private Sub New()
            End Sub
        End Class
    End Class
End Namespace

